简单的小型INotifyPropertyChanged实现

时间:2010-10-03 18:59:06

标签: wpf inotifypropertychanged

说我有以下课程:

public MainFormViewModel
{
    public String StatusText {get; set;}
}

对StatusText进行更改以反映绑定到它的任何控件的最简单最简单方法是什么?

显然我需要使用INotifyPropertyChanged,但有没有一种很酷的方法来做到这一点,不会弄乱我的代码?需要大量文件?等?

注意:如果这是一个骗局,那么我很抱歉。我搜索过但找不到任何东西,但使用T4 code Generation这听起来并不容易(至少要设置)。

6 个答案:

答案 0 :(得分:5)

不幸的是,C#没有提供一种自动执行此操作的简单机制......创建这样的新语法已经suggested

public observable int Foo { get; set; }

但我怀疑它会被包含在语言中......

一种可能的解决方案是使用像Postsharp这样的AOP框架,这样你只需要用属性来装饰你的属性:

public MainFormViewModel : INotifyPropertyChanged
{
    [NotifyPropertyChanged]
    public String StatusText {get; set;}
}

(还没试过,但我很确定Postsharp允许你做那种事情......)


更新:好的,我设法让它发挥作用。请注意,这是一个非常粗略的实现,使用私有字段上的反射来检索委托......它当然可以改进,但我会留给你;)

[Serializable]
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
{
    public override void OnSetValue(LocationInterceptionArgs args)
    {
        object oldValue = args.GetCurrentValue();
        object newValue = args.Value;
        base.OnSetValue(args);
        if (args.Instance is INotifyPropertyChanged)
        {
            if (!Equals(oldValue, newValue))
            {
                RaisePropertyChanged(args.Instance, args.LocationName);
            }
        }
    }

    private void RaisePropertyChanged(object instance, string propertyName)
    {
        PropertyChangedEventHandler handler = GetPropertyChangedHandler(instance);
        if (handler != null)
            handler(instance, new PropertyChangedEventArgs(propertyName));
    }

    private PropertyChangedEventHandler GetPropertyChangedHandler(object instance)
    {
        Type type = instance.GetType().GetEvent("PropertyChanged").DeclaringType;
        FieldInfo propertyChanged = type.GetField("PropertyChanged",
                                                  BindingFlags.Instance | BindingFlags.NonPublic);
        if (propertyChanged != null)
            return propertyChanged.GetValue(instance) as PropertyChangedEventHandler;

        return null;
    }
}

请注意,您的类仍需要实现INotifyPropertyChanged接口。您只需在属性设置器中明确提升事件即可。

答案 1 :(得分:3)

快照http://code.google.com/p/notifypropertyweaver/

您需要做的就是实施INotifyPropertyChanged

所以你的代码看起来像

public MainFormViewModel : INotifyPropertyChanged
{
    public String StatusText {get; set;}

    #region INotifyPropertyChanged Implementation
}

构建任务将编译这个(你永远不会看到下面的代码)

public MainFormViewModel : INotifyPropertyChanged
{
    public String StatusText {get; set;}
    private string statusText;

    public string StatusText 
    {
       get { return statusText; }
       set
       {
           if (value!= statusText)
           {
               statusText = value;
               OnPropertyChanged("StatusText");
           }
       }
    }

    #region INotifyPropertyChanged Implementation
}

答案 2 :(得分:2)

通过利用EqualityComparer.Default,您可以将属性设置器代码减少到一行,如下所示:

private int unitsInStock;
public int UnitsInStock 
{
  get { return unitsInStock; }
  set { SetProperty(ref unitsInStock, value, "UnitsInStock"); }
}

public event PropertyChangedEventHandler PropertyChanged;

protected void SetProperty<T>(ref T field, T value, string name) 
{
  if (!EqualityComparer<T>.Default.Equals(field, value)) 
  {
    field = value;
    var handler = PropertyChanged;
    if (handler != null) 
    {
      handler(this, new PropertyChangedEventArgs(name));
    }
  }
}

如果视图模型继承自定义SetProperty方法和PropertyChanged事件的基类,那么在子视图模型中支持INotifyPropertyChanged所需的代码量将变得非常小(1行)。

这种方法比其他答案中提到的代码编织方法更详细,但不要求您修改构建过程来完成它。

请务必查看即将发布的C# 5 Caller Info attributes,看起来它们可以让我们避免在方法中使用魔术字符串而不会产生反射的性能成本。

更新(2012年3月1日):

.NET 4.5 Beta已经用完了,有了它,您可以进一步优化上面的代码,从而消除了调用者对字符串文字的需求:

private int unitsInStock;
public int UnitsInStock
{
    get { return unitsInStock; }
    set 
    { 
        SetProperty(ref unitsInStock, value);
    }
}

public event PropertyChangedEventHandler PropertyChanged;

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
    if (!EqualityComparer<T>.Default.Equals(field, value))
    {
        field = value;
        var handler = PropertyChanged;
        if (handler != null)
        {
          handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

我有blog post更详细地谈论它。

答案 3 :(得分:1)

我一直很喜欢这种方法

private string m_myString;
public string MyString
{
    get { return m_myString; }
    set 
    {
        if (m_myString != value)
        {
             m_myString = value;
             NotifyPropertyChanged("MyString");
        }
    }
}


private void NotifyPropertyChanged(string property)
{
    if (PropertyChanged != null)
         PropertyChanged(this, new PropertyChangedEventArgs(property));
}

或更少的代码膨胀

set 
{
    m_myString = value;
    NotifyPropertyChanged("MyString");
}

答案 4 :(得分:0)

我有一个名为“Model”的基类。它公开了一个名为DataPoints的受保护对象,它本质上是一个字典。

C#

public String StatusText {
    get { 
        return (string)DataPoints["StatusText"]; 
    } 
    set { 
        DataPoints["StatusText"] = value; 
    }
}

VB

public Property StatusText as String 
    get 
        return DataPoints!StatusText 
    end get 
    set
        DataPoints!StatusText = value
    end set
end property

在DataPoints字典中设置值时,它会执行以下操作:

  1. 检查以确保实际更改的值。
  2. 保存新值
  3. 将IsDirty属性设置为true。
  4. 引发指定属性的Property Changed事件以及IsDirty和IsValid属性。
  5. 由于它是一个字典,它还使得从数据库或XML文件加载对象变得非常容易。

    现在您可能认为阅读和写入字典是昂贵的,但我一直在进行大量的性能测试,而且我在WPF应用程序中没有发现任何明显的影响。

答案 5 :(得分:0)

PropertyChanged.Fody NuGet包执行此操作。

https://github.com/Fody/PropertyChanged

  • PropertyChanged.Fody包添加到您的项目中。
  • 在您的模型中引用 PropertyChanged using PropertyChanged;
  • [ImplementPropertyChanged]属性添加到您的班级。

该类中的所有属性现在都会神奇地实现INotifyPropertyChanged。注意 - Fody通过修改发出的IL来工作,所以你永远不会真正看到VS中的代码 - 它只是神奇地做到了。

其他文档: https://github.com/Fody/PropertyChanged/wiki/Attributes