【问题标题】:Make a class suitable for Property Change Notification制作适合 Property Change Notification 的类
【发布时间】:2015-06-08 20:43:22
【问题描述】:

要在 WPF 中实现数据绑定,根据 MS 的说法,“类需要提供适当的属性更改通知”。 [ref here]

AFAIK,设置的一部分意味着采取以下步骤,如果尚未在课堂上设置(参考 MSDN 上的 this article):

  • 属性发生变化时,需要调用方法来引发事件。
    • 这意味着必须更改自动实现的属性,以便它们使用私有支持字段,因此set 可以更改属性并调用方法来引发事件。
  • 该类需要实现 INotifyPropertyChanged。
  • 该类需要声明PropertyChangedEventHandler事件。
  • 需要以如下方式引发事件:

...

// Create the OnPropertyChanged method to raise the event 
protected void OnPropertyChanged(string name)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(name));
    }
}

如果我已经有许多不采取任何这些步骤的现有课程,那么更改这些课程的最佳方法是什么,以便它们仅在使它们符合这些标准所需的范围内进行更改 ?

【问题讨论】:

  • 需要变量handler 有什么特别的原因吗?
  • @O.R.Mapper 这是用于以线程安全方式引发事件的标准 C# 模式(至少在 C#6 之前)。 Eric Lippert 解释了所有here
  • 澄清一下,我知道如何手动实现这一点,但希望有更多类似 DRY 的东西。
  • @Olly:很有趣,谢谢。在我读过的任何 C# 书籍中都没有遇到过这种情况(尽管所有这些都是在 C# 的早期编写的)。

标签: c# wpf


【解决方案1】:

如果您想要最小可能代码影响的解决方案,那么您想要Fody PropertyChanged weaver。这是作为带有Install-Package PropertyChanged.Fody 的 NuGet 包安装的,或者通过 VS 包管理器对话框安装。

安装后,您可以使用[ImplementPropertyChanged] 属性标记一个类,您的工作就基本完成了。 Weaver 将通过操作生成的 IL在编译时添加相关的事件调用,这意味着您不必在代码中显式实现接口或事件调用。您也可以保留自动实现的属性语法!

链接的文档提供了更高级案例的详细信息,但根据我的经验,开箱即用的行为足以满足几乎所有需求。

【讨论】:

  • 对于存在大量现有类的情况,这看起来是一个很好的选择。您应该提到,INPC 实现应该在它们都派生自的基类中完成。
  • 不,我不应该,因为您不需要使用基类,甚至不需要显式实现INotifyPropertyChanged。您只需添加属性即可。它也是全新项目的绝佳选择,因为它只是删除了所有样板。
  • 去过那里,尝试过 - 并且 10% 的时间都没有工作。所以我最终只是手动完成。与 PostSharp 相同。
  • 在什么情况下不起作用?我在使用 Fody 时从来没有遇到过任何问题。没试过 PostSharp。
  • 那么你的意思是它会在你装饰的每个类上完全独立地实现它?如果是这样,我会对使用它有两种看法 - 让所有类易于通知与长期代码可维护性和 OO 正确性。
【解决方案2】:

我喜欢在 C# 5 中使用 [CallerMemberName] 的这种实现,如 here 所述

protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
    ...
}

只需将参数设为可选并使用 CallerMemberName 属性对其进行装饰。

您的属性现在如下所示:

public DateTime Time
{
    get { return this.model.DateTime; }
    set
    {
        this.model.DateTime = value;
        NotifyPropertyChanged();
    }
}

【讨论】:

  • 这是一个非常好的方法,我喜欢它
  • 这里要注意的一点是,如果属性实际上没有更改,您可能不想触发通知;在最好的情况下这可能会有点浪费,甚至在循环属性更新的最坏情况下会导致堆栈溢出。我更喜欢使用包装器 SetProperty 方法,该方法通过引用获取字段以及 value 以及 [CallerMemberName] 参数。它仅在值实际更改并返回 bool 时触发,因此您可以使用有关它是否更改的信息来执行 setter 中的其他逻辑。
【解决方案3】:

我通常喜欢使用这个实现来通知我的属性的变化

class IWillNotifyYou : INotifyPropertyChanged
{
    private int _firstProperty;
    public int FirstProperty
    {
        get { return _firstProperty; }
        set
        {
            if (value != _firstProperty)
            {
                _firstProperty = value;
                OnPropertyChanged("FirstProperty");
            }
        }
    }

    private string _secondProperty;
    public string SecondProperty
    {
        get { return _secondProperty; }
        set
        {
            if (value != _secondProperty)
            {
                _secondProperty = value;
                OnPropertyChanged("SecondProperty");
            }
        }
    }

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


    public event PropertyChangedEventHandler PropertyChanged;
}

请注意,您可以使用属性名称(如 FirstPropertyChanged)创建一个专门的事件

【讨论】:

  • 这是正确的,但我希望做的不是手动重写所有现有的类。
【解决方案4】:

您需要确定现有课程现在所扮演的角色。 WPF 使用 MVVM(模型-视图-视图模型)是最有益的。确定当前班级的角色将有助于确定需要更改的内容(如果有的话)。

假设您现有的类是业务对象,则由您决定是否需要实现INotifyPropertyChanged。通过绑定到ViewModel,WPF 最容易使用。 ViewModel 实际处理View(WPF 屏幕)和Model 之间的数据。

Model 可以实现INotifyPropertyChanged,但不是必须的。 ViewModel 将包含特定屏幕(或控件)所需的任何属性,并且可能是您想要探索的路径,因此您不会完全 180* 现有业务对象。

【讨论】:

    【解决方案5】:

    在你的类定义之后添加“:INotifyPropertyChanged”:

    Public class Test : INotifyPropertyChanged 
    {
    }
    

    接受 ReSharpers 建议以实现此类接口(您可能必须安装 ReSharper 才能使其工作)。

    然后,在任何属性的设置器中,添加以下内容:

    OnPropertyChanged("PropertyName")
    

    这意味着您现在可以将 XAML 中的属性绑定到此类中的此属性。

    【讨论】:

    • 为了安全起见,OnPropertyChanged 只有在属性值真正改变时才应该被调用。否则,您可能会遇到丑陋的振荡无限循环问题,这些属性会通过再次重复分配相同的“新”值来不断地相互更新。
    • 理论上,您可以使用 PostSharp 自动为您执行此操作,但在实践中,您会发现 (a) 这是您最不关心的问题,并且 (b) 如果您安装了 ReSharper,它有很多可以自动完成的功能,因此通常只需几次击键。
    • @AaronThomas 有工具会在编译过程中自动添加,PostSharp is one example。 (免费版本只允许您在每个项目的 10 个类上使用它)。 Unity 是另一个例子,但它涉及more manual coding 编写拦截,您必须通过 Unity 解析您的对象。
    • 最后,另一种模式是创建一个具有OnPropertyChangedSetProperty 方法的ViewModelBase 类。然后你只需在 setter 中执行SetProperty(ref _propertyName, value);
    • 我第一次遇到 SetProperty 方法的地方是来自PrismBindableBase 就是他们所说的我所说的ViewModelBase。此外,如果您使用 ReSharper,您可以将 [NotifyPropertyChangedInvocator] 放在 SetProperty 上,它将用于右键单击菜单的“使用 INotifyPropertyChanged 的​​属性”,他们添加了对 bool FunctionName(ref object, string) 签名的支持以与 Prism 兼容。跨度>
    猜你喜欢
    • 2022-12-19
    • 1970-01-01
    • 1970-01-01
    • 2014-06-27
    • 2018-02-10
    • 2023-03-21
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多