简单的小型 INotifyPropertyChanged 实现

发布于 2024-09-26 02:17:38 字数 452 浏览 2 评论 0原文

假设我有以下课程:

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

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

显然我需要使用 INotifyPropertyChanged,但是有没有一种很酷的方法可以做到这一点,并且不会使我的代码变得混乱?需要很多文件吗? ETC?

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

Say I have the following class:

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

What is the easiest smallest way to get my changes to StatusText to reflect to any controls that bind to it?

Obviously I need to use INotifyPropertyChanged, but is there a cool way to do it that does not clutter up my code? need lots of files? etc?

Note: If this is a dupe then I am sorry. I searched and could not find any thing but using T4 code Generation which does not sound easy (to setup at least).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

不及他 2024-10-03 02:17:38

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

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接口。您只是不必在属性设置器中显式引发该事件。

Unfortunately C# doesn't offer an easy mechanism to do that automatically... It has been suggested to create a new syntax like this :

public observable int Foo { get; set; }

But I doubt it will ever be included in the language...

A possible solution would to use an AOP framework like Postsharp, that way you just need to decorate your properties with an attribute:

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

(haven't tried, but I'm pretty sure Postsharp allows you to do that kind of thing...)


UPDATE: OK, I managed to make it work. Note that it's a very crude implementation, using reflection on a private field to retrieve the delegate... It could certainly be improved, but I'll leave it to you ;)

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

Note that your class still need to implement the INotifyPropertyChanged interface. You just don't have to explicitly raise the event in your property setters.

等往事风中吹 2024-10-03 02:17:38

尝试一下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
}

Have a go of this http://code.google.com/p/notifypropertyweaver/

All you need to do is implement INotifyPropertyChanged

So your code will look like

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

    #region INotifyPropertyChanged Implementation
}

The build task will compile this (you never see the below code)

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
}
凯凯我们等你回来 2024-10-03 02:17:38

通过利用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 调用者信息属性 以及看起来它们将允许我们避免在方法中使用魔术字符串,而不需要反射的性能成本。

更新(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));
        }
    }
}

我有一个 博客文章 对此进行了更详细的讨论。

By leveraging EqualityComparer.Default you can reduce the property setter code down to one line as follows:

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

If your view models inherit from a base class that defines the SetProperty method and the PropertyChanged event, then the amount of code required to support INotifyPropertyChanged in your child view models becomes very minimal (1 line).

This approach is more verbose then the code weaving methods mentioned in other answers, but doesn't require you to modify your build process to accomplish it.

Be sure to take a look at the upcoming C# 5 Caller Info attributes as well as it looks like they will allow us to avoid using a magic string in the method without the performance cost of reflection.

UPDATE (March 1st, 2012):

The .NET 4.5 Beta is out, and with it, you can further refine the above code to this which removes the need for the string literal in the caller:

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

I have a blog post that talks about it in slightly more detail.

我是有多爱你 2024-10-03 02:17:38

我一直喜欢这种方法

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

Ive always liked this method

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

or for less code bloat

set 
{
    m_myString = value;
    NotifyPropertyChanged("MyString");
}
年少掌心 2024-10-03 02:17:38

我有一个名为“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. 引发指定属性以及 IsDirty 和 IsValid 属性的 Property Changed 事件。

由于它是字典,因此它还使得从数据库或 XML 文件加载对象变得非常容易。

现在您可能认为读取和写入字典的成本很高,但我一直在进行大量性能测试,并且在我的 WPF 应用程序中没有发现这有任何明显的影响。

I have a base class called "Model". It exposes a protected object called DataPoints, which is essentially a dictionary.

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

When you set a value in the DataPoints dictionary it does the following:

  1. Checks to make sure the value actually changed.
  2. Saves the new value
  3. Sets the IsDirty property to true.
  4. Raises the Property Changed event for the named property as well as the IsDirty and IsValid properties.

Since it is a dictionary, it also makes loading objects from a database or XML file really easy.

Now you may think reading and writing to dictionary is expensive, but I've been doing a lot of performance testing and I haven't found any noticable impact from this in my WPF applications.

风启觞 2024-10-03 02:17:38

PropertyChanged.Fody NuGet 包执行此操作。

https://github.com/Fody/PropertyChanged

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

类中的所有属性现在都将神奇地实现 INotifyPropertyChanged。注意 - Fody 通过修改发出的 IL 来工作,因此您永远不会在 VS 中实际看到代码 - 它只是神奇地做到了这一点。

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

The PropertyChanged.Fody NuGet package does this.

https://github.com/Fody/PropertyChanged

  • Add the PropertyChanged.Fody package to your project.
  • Reference PropertyChanged in your model: using PropertyChanged;
  • Add the [ImplementPropertyChanged] attribute to your class.

All of the properties in the class will now magically implement INotifyPropertyChanged. Note - Fody works by modifying the emitted IL so you will never actually see the code in VS - it just magically does it.

Additional docs:
https://github.com/Fody/PropertyChanged/wiki/Attributes

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文