触发OnPropertyChanged的更好方法

时间:2011-08-15 10:26:54

标签: c# wpf

我们有一个遵循MVVM模式的WPF项目。

在View Model中有很多代码如下所示:

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

有没有办法做到这一点需要更少的代码?

对于这样的事情会很好:

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

6 个答案:

答案 0 :(得分:11)

您可以查看PostSharp。他们甚至在Data Binding有一个样本。代码取自那里:

/// <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 );

    }
}

然后使用就像这样简单:

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

从PostSharp网站获取并插入以完成答案的示例

答案 1 :(得分:5)

看起来好像Framework 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的Framework 4.0,则可以通过nuget安装 Microsoft BCL兼容包,它也提供此属性。

答案 2 :(得分:3)

Josh Smith有一篇关于使用DynamicObject执行此操作的文章here

基本上它涉及从DynamicObject继承然后挂钩到TrySetMember。不幸的是,CLR 4.0虽然在早期版本中也可能使用ContextBoundObject,但这可能会损害性能,主要适用于远程处理\ WCF。

答案 3 :(得分:2)

恕我直言,PostSharp方法,如在接受的答案中,非常好,当然是直接回答问题。

但是,对于那些不能或不会使用像PostSharp这样的工具扩展C#语言语法的人来说,可以通过实现{{{{ 1}}。有很多例子,但到目前为止还没有一个包含在这个有用且流量很大的问题中,所以这里是我通常使用的版本:

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));
    }
}

不像在PostSharp方法中将代码属性应用于自动实现的属性那样简洁,但仍然可以大大加快视图模型和其他类似类型的实现。

上面的关键功能将其与其他一些实现区别开来:

  1. 使用private int _value; public int Value { get { return _value; } set { UpdatePropertyField(ref _value, value); } } 比较平等。这可以确保可以在不加框的情况下比较值类型(常见的替代方法是EqualityComparer<T>.Default)。 object.Equals(object, object)实例已缓存,因此在对任何给定类型IEqualityComparer<T>进行第一次比较后,它非常有效。
  2. T方法为OnPropertyChanged()。这允许派生类型以集中方式轻松有效地处理属性更改事件,而无需订阅virtual事件本身(例如,用于多级继承),当然也可以使派生类型更好地控制如何当它处理相对于提升实际PropertyChanged事件的属性更改事件时。

答案 4 :(得分: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;

然后将光标放在该行的某处并运行此宏。同样,这将使用公共属性替换该行,因此请确保在您的类中具有相同的私有成员变量。

我确信这个脚本可以清理干净,但它为我节省了数小时的繁琐工作。

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

答案 5 :(得分:0)

I'd use PropertyChanged.Fody NuGet package. It's as simple as:

[PropertyChanged.ImplementPropertyChanged]
public class MyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
}

All public properties will raise PropertyChanged event under the hood.

P.S. Syntax changed in newer versions. This example is for version 1.52.1