2011-08-15 11 views
18

우리는 MVVM 패턴을 따르는 WPF 프로젝트를 가지고 있습니다.OnPropertyChanged를 트리거하는 더 나은 방법

private string m_Fieldname; 
    public string Fieldname 
    { 
     get { return m_Fieldname; } 
     set 
     { 
      m_Fieldname = value; 
      OnPropertyChanged("Fieldname"); 
     } 
    } 

적은 코드를 필요로 할 수있는 방법이있다 : 뷰 모델에서

다음과 같습니다 많은 코드가?

이 같은 좋은 될 것이다 :

[NotifyWhenChanged] 
public string Fieldname { get; set ; } 
+1

'if (m_Fieldname! = value) {...}'도 항상 확인하지 않아야하나요? 더 많은 코드입니다.하지만 속성이 변경되지 않으면 'PropertyChanged'키를 올리는 것이 올바르게되지 않는 것 같습니다. –

+0

@Danko, 감사합니다. 좋은 점 –

+0

개인적으로 모든 알림, 동등성 검사, 속성 설정 등을 처리하는 Base ObservableItem 클래스의 SetProperty 메서드가 있습니다. 내 ViewModelBase에서 파생 된 클래스입니다. 여전히 단 하나의 라이너를 가져오고 설정합니다. 또한 코드 스 니펫을 생성하기 위해 코드 스 니펫을 설정하고 빠르고 간단하며 표준화되었습니다. –

답변

11

당신은 PostSharp에 모습을 가질 수 있습니다. 심지어 Data Binding에 샘플이 있습니다. 코드는 거기에서 촬영 :

+0

X가 OnPropertyChanged를 실행하지 않게하려면 어떻게해야합니까? – thenonhacker

+1

@thenonhacker 'IgnoreAutoChangeNotificationAttribute'를 사용하여 속성을 장식하여 무시할 수 있습니다. 설명서를 참조하십시오. http://doc.postsharp.net/##T_PostSharp_Patterns_Model_NotifyPropertyChangedAttribute 및 http://doc.postsharp.net/##T_PostSharp_Patterns_Model_IgnoreAutoChangeNotificationAttribute – Sascha

+0

Nice! 감사 Sascha! – thenonhacker

3

조쉬 스미스는 좋은 기사를에있는 대답을 완료 PostSharp 사이트에서 가져온 삽입

[NotifyPropertyChanged] 
public class Shape 
{ 
    public double X { get; set; } 
    public double Y { get; set; } 
} 

예 :

/// <summary> 
/// Aspect that, when apply on a class, fully implements the interface 
/// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to 
/// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>. 
/// </summary> 
[Serializable] 
[IntroduceInterface(typeof(INotifyPropertyChanged), 
        OverrideAction = InterfaceOverrideAction.Ignore)] 
[MulticastAttributeUsage(MulticastTargets.Class, 
          Inheritance = MulticastInheritance.Strict)] 
public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect, 
                INotifyPropertyChanged 
{ 

    /// <summary> 
    /// Field bound at runtime to a delegate of the method <c>OnPropertyChanged</c>. 
    /// </summary> 
    [ImportMember("OnPropertyChanged", IsRequired = false)] 
    public Action<string> OnPropertyChangedMethod; 

    /// <summary> 
    /// Method introduced in the target type (unless it is already present); 
    /// raises the <see cref="PropertyChanged"/> event. 
    /// </summary> 
    /// <param name="propertyName">Name of the property.</param> 
    [IntroduceMember(Visibility = Visibility.Family, IsVirtual = true, 
         OverrideAction = MemberOverrideAction.Ignore)] 
    public void OnPropertyChanged(string propertyName) 
    { 
     if (this.PropertyChanged != null) 
     { 
      this.PropertyChanged(this.Instance, 
            new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    /// <summary> 
    /// Event introduced in the target type (unless it is already present); 
    /// raised whenever a property has changed. 
    /// </summary> 
    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)] 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// Method intercepting any call to a property setter. 
    /// </summary> 
    /// <param name="args">Aspect arguments.</param> 
    [OnLocationSetValueAdvice, 
    MulticastPointcut(Targets = MulticastTargets.Property, 
     Attributes = MulticastAttributes.Instance)] 
    public void OnPropertySet(LocationInterceptionArgs args) 
    { 
     // Don't go further if the new value is equal to the old one. 
     // (Possibly use object.Equals here). 
     if (args.Value == args.GetCurrentValue()) return; 

     // Actually sets the value. 
     args.ProceedSetValue(); 

     // Invoke method OnPropertyChanged (our, the base one, or the overridden one). 
     this.OnPropertyChangedMethod.Invoke(args.Location.Name); 

    } 
} 

사용법은 다음이만큼 간단합니다 이렇게하려면 DynamicObject를 사용하십시오 here

기본적으로 DynamicObject를 상속 한 다음 TrySetMember에 연결합니다. 불행히도 이전 버전에서는 ContextBoundObject를 사용하는 것이 가능할 수도 있지만 이는 주로 성능 저하를 초래할 수 있으며 주로 remoting \ WCF에 적합합니다.

+2

성능 손실이 있습니까? 다이나믹이 매우 느리고 속성 변경이 자주 발생할 수 있습니다. 내가 틀린가요? –

+0

try/catch 블록이 포함되어 있기 때문에 고전적인 방법보다 확실히 느립니다. 평소처럼 트레이드 오프가 있습니다. –

5

그것은 프레임 워크 4.5이 약간 단순화 것처럼 보이는이 :

private string m_Fieldname; 
public string Fieldname 
{ 
    get { return m_Fieldname; } 
    set 
    { 
     m_Fieldname = value; 
     OnPropertyChanged(); 
    } 
} 

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed") 
{ 
    // ... do stuff here ... 
} 

이 매우 당신이 찾고있는 정도 일을 자동화하지만 CallerMemberNameAttribute를 사용하는 것은 같은 속성 이름을 통과하게하지 않습니다 문자열이 필요하지 않습니다.

하면 설치 KB2468871와 함께, 당신은 또한이 속성을 제공 nuget,를 통해 마이크로 소프트 BCL 호환 기능 팩를 설치할 수 있습니다 프레임 워크 4.0에서 작업하는 경우.

0

이 코드를 정리하지는 않지만이 코드를 모두 작성하는 시간이 단축됩니다. 몇 분 안에 20 개 이상의 속성 목록을 사용할 수 있습니다.

먼저 모든 개인 변수를 정의해야합니다. 첫 번째 문자는 소문자라고 가정합니다. 매크로가 원래 줄을 제거 할 때 이제 이러한 변수를 다른 목록에 복사하십시오. 예를 들어

:

private int something1 = 0; 
private int something2 = 0; 
private int something3 = 0; 
private int something4 = 0; 
private int something5 = 0; 
private int something6 = 0; 

그런 다음 그 줄에 어딘가에 커서를 놓고이 매크로를 실행합니다. 다시 이것은 public 속성으로 라인을 대체하므로 클래스에서이 전에 정의 된 것과 동일한 private 멤버 변수가 있는지 확인하십시오.

이 스크립트는 정리할 수 있지만 오늘 지겨운 업무 시간을 절약 해주었습니다.

Sub TemporaryMacro() 
    DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText) 
    DTE.ActiveDocument.Selection.Delete(7) 
    DTE.ActiveDocument.Selection.Text = "public" 
    DTE.ActiveDocument.Selection.CharRight() 
    DTE.ExecuteCommand("Edit.Find") 
    DTE.Find.FindWhat = " " 
    DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument 
    DTE.Find.MatchCase = False 
    DTE.Find.MatchWholeWord = False 
    DTE.Find.Backwards = False 
    DTE.Find.MatchInHiddenText = False 
    DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral 
    DTE.Find.Action = vsFindAction.vsFindActionFind 
    If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then 
     Throw New System.Exception("vsFindResultNotFound") 
    End If 
    DTE.ActiveDocument.Selection.CharRight() 
    DTE.ActiveDocument.Selection.WordRight(True) 
    DTE.ActiveDocument.Selection.CharLeft(True) 
    DTE.ActiveDocument.Selection.Copy() 
    DTE.ActiveDocument.Selection.CharLeft() 
    DTE.ActiveDocument.Selection.CharRight(True) 
    DTE.ActiveDocument.Selection.ChangeCase(VsCaseOptions.VsCaseOptionsUppercase) 
    DTE.ActiveDocument.Selection.EndOfLine() 
    DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText) 
    DTE.ExecuteCommand("Edit.Find") 
    DTE.Find.FindWhat = " = " 
    DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument 
    DTE.Find.MatchCase = False 
    DTE.Find.MatchWholeWord = False 
    DTE.Find.Backwards = False 
    DTE.Find.MatchInHiddenText = False 
    DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral 
    DTE.Find.Action = vsFindAction.vsFindActionFind 
    If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then 
     Throw New System.Exception("vsFindResultNotFound") 
    End If 
    DTE.ActiveDocument.Selection.CharLeft() 
    DTE.ActiveDocument.Selection.EndOfLine(True) 
    DTE.ActiveDocument.Selection.Delete() 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "{" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "get { return " 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = "; }" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "set" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "{" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "if(" 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = " != value)" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "{" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = " = value;" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "OnPropertyChanged(""" 
    DTE.ActiveDocument.Selection.Paste() 
    DTE.ActiveDocument.Selection.Text = """);" 
    DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText) 
    DTE.ExecuteCommand("Edit.Find") 
    DTE.Find.FindWhat = """" 
    DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument 
    DTE.Find.MatchCase = False 
    DTE.Find.MatchWholeWord = False 
    DTE.Find.Backwards = False 
    DTE.Find.MatchInHiddenText = False 
    DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral 
    DTE.Find.Action = vsFindAction.vsFindActionFind 
    If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then 
     Throw New System.Exception("vsFindResultNotFound") 
    End If 
    DTE.ActiveDocument.Selection.CharRight() 
    DTE.ActiveDocument.Selection.CharRight(True) 
    DTE.ActiveDocument.Selection.ChangeCase(VsCaseOptions.VsCaseOptionsUppercase) 
    DTE.ActiveDocument.Selection.Collapse() 
    DTE.ActiveDocument.Selection.EndOfLine() 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "}" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "}" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.Text = "}" 
    DTE.ActiveDocument.Selection.NewLine() 
    DTE.ActiveDocument.Selection.LineDown() 
    DTE.ActiveDocument.Selection.EndOfLine() 
End Sub 
2

IMHO는 허용 대답에서와 PostSharp 접근 방식은, 아주 좋은 물론 묻는 질문에 직접 답변입니다.

그러나 PostSharp와 같은 도구를 사용하여 C# 언어 구문을 확장 할 수 없거나 사용하지 않는 사용자의 경우 INotifyPropertyChanged을 구현하는 기본 클래스를 사용하여 코드 반복을 피할 수있는 대부분의 이점을 얻을 수 있습니다.

/// <summary> 
/// Base class for classes that need to implement <see cref="INotifyPropertyChanged"/> 
/// </summary> 
public class NotifyPropertyChangedBase : INotifyPropertyChanged 
{ 
    /// <summary> 
    /// Raised when a property value changes 
    /// </summary> 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// Updates a field for a named property 
    /// </summary> 
    /// <typeparam name="T">The type of the field</typeparam> 
    /// <param name="field">The field itself, passed by-reference</param> 
    /// <param name="newValue">The new value for the field</param> 
    /// <param name="propertyName">The name of the associated property</param> 
    protected void UpdatePropertyField<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null) 
    { 
     if (!EqualityComparer<T>.Default.Equals(field, newValue)) 
     { 
      field = newValue; 
      OnPropertyChanged(propertyName); 
     } 
    } 

    /// <summary> 
    /// Raises the <see cref="PropertyChanged"/> event. 
    /// </summary> 
    /// <param name="propertyName">The name of the property that has been changed</param> 
    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChanged?.DynamicInvoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

예를 들어, 사용, 같은 :

이 주위에 거짓말을 많은 예제가 있지만 어느 것도 지금까지 유용하고 잘 인신 매매 문제에 포함되어 있지 않은, 그래서 여기에 내가 일반적으로 사용하는 버전입니다
private int _value; 
public int Value 
{ 
    get { return _value; } 
    set { UpdatePropertyField(ref _value, value); } 
} 

PostSharp 방식처럼 자동 구현 된 속성에 코드 속성을 적용 할 수있는 것처럼 간결하지만 아직보기 모델 및 기타 유사한 유형의 구현 속도를 높이는 방법은 아직 멀었습니다. 그 위의

주요 기능은 다른 구현과 구별 :

  1. 평등은 EqualityComparer<T>.Default를 사용하여 비교됩니다. 이렇게하면 값 유형을 박스화하지 않고 비교할 수 있습니다 (일반적인 대안은 object.Equals(object, object)). IEqualityComparer<T> 인스턴스가 캐시되므로 특정 유형 T에 대한 첫 번째 비교 이후에 매우 효율적입니다.
  2. OnPropertyChanged() 방법은 virtual입니다. 이를 통해 파생 된 유형은 PropertyChanged 이벤트 (예 : 여러 레벨의 상속)에 등록 할 필요없이 중앙에서 변경된 속성을 쉽고 효율적으로 처리 할 수 ​​있으며 물론 파생 된 유형을 처리하는 방법 및시기를 더 잘 제어 할 수 있습니다 속성이 실제로 PropertyChanged 이벤트를 발생시키는 것과 관련하여 이벤트를 변경했습니다.