2010-10-03 96 views
4

说我有以下类:简单的小INotifyPropertyChanged的实施

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

是什么让我改变状态文本,以反映到绑定到它的任何控件的最简单的方式最小?

很明显,我需要使用INotifyPropertyChanged,但有没有一种很酷的方式来做到这一点,不会混乱我的代码?需要很多文件?等等?

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

回答

4

不幸的是C#不提供了一个简单的机制来做到这一点自动...这已经suggested创建一个新的语法如下:

public observable int Foo { get; set; } 

但我怀疑它永远不会被包含在语言...

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

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

(没试过,但我敢肯定Postsharp允许你做那种事......)


UPDATE:OK,我设法使它工作。请注意,这是一个非常粗略的实现,用一个私人领域的反射来获取委托...这当然可以得到改善,但我要把它留给你;)

[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接口。你只需要在你的属性设置器中明确提出事件。

+0

你确定这是链接到Postsharp。看起来他们似乎没有为C#提供任何工具。 – Vaccano 2010-10-03 19:10:42

+0

哎呀,对不起,坏的链接...我修好了它 – 2010-10-03 19:12:36

+0

这看起来很酷,但它必须在商业版本(或比它看起来更难)。我无法使它与Community Edition一起工作。 – Vaccano 2010-10-03 20:02:58

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"); 
} 
+1

Val。在多线程应用程序中,您应该先调用PropertyChanged的本地副本。原因是代码“if(PropertyChanged!= null)”可能会在您调用PropertyChanged时通过bu,它可能为null。我知道极端边缘情况,但可能很难调试。 – Simon 2010-10-04 02:29:15

+0

我没有考虑过这种情况。感谢您的高举。 – Val 2010-10-04 02:41:41

+1

Simon有一个很好的观点,但是一般来说属性更改通知只允许在UI线程中使用。如果修改“PropertyChanged”事件的线程不是引发事件的线程,那么您的代码中可能有一个错误。 – Gabe 2010-10-04 04:15:58

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 
} 
0

我有所谓的“模型”的基类。它公开了一个名为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 

当你设置数据点的值字典将执行以下操作:

  1. 检查以确保值实际改变。
  2. 保存新值
  3. 将IsDirty属性设置为true。
  4. 引发指定属性的Property Changed事件以及IsDirty和IsValid属性。

由于它是一个字典,它也使得从数据库或XML文件加载对象非常容易。

现在你可能认为阅读和写字典是昂贵的,但我一直在做很多性能测试,并且我没有在WPF应用程序中发现任何明显的影响。

1

通过利用EqualityComparer.Default您可以向下降低属性setter代码一行如下:

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,稍微更详细地谈论它。

0

PropertyChanged.Fody NuGet包做到这一点。

https://github.com/Fody/PropertyChanged

  • PropertyChanged.Fody包添加到您的项目。
  • 参考在模型中的PropertyChangedusing PropertyChanged;
  • [ImplementPropertyChanged]属性添加到您的类。

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

附加文档: https://github.com/Fody/PropertyChanged/wiki/Attributes