wpf 不了解INotifyPropertyChanged函数的作用

7gyucuyw  于 2022-12-14  发布在  其他
关注(0)|答案(4)|浏览(166)

INotifyPropertyChanged接口实现(appropriate event)和helper方法在我的代码中工作得很好,但我真的不明白它是如何工作的。在我看来,我的书没有很好地向我解释它,或者我不是很聪明。我们有一个单独的类Car.cs添加到我的解决方案中,代码如下(书中的示例),它应该在双向绑定中工作,因为当对象的示例也被更改时,我的WPF应用程序中的TextBox控件也被更改:

public class Car: INotifyPropertyChanged
{
private string _make;
private string _model;

public event PropertyChangedEventHandler PropertyChanged;

public Car()
{

}
    
public string Make
{
    get { return _make; }
    set
    {
        if (_make != value)
        {
            _make = value;
            OnPropertyChanged("Make");
        }
    }
}
public string Model
{
    get { return _model; }
    set
    {
        if(_model != value)
        {
            _model = value;
            OnPropertyChanged("Model");
        }
    }
}

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

这里是我学习微软学习材料后自己做的代码,它看起来和工作更好海事组织:

public class Car: INotifyPropertyChanged
{
private string _make; 
private string _model;

public event PropertyChangedEventHandler PropertyChanged;

public Car()
{

}
    
public string Make
{
    get { return _make; }
    set
    { 
        _make = value;
        OnPropertyChanged(); 
    }
}
public string Model
{
    get { return _model; }
    set
    {
        _model = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged ()
{
    if (PropertyChanged != null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(""));
    }
} }

所以我有一个愚蠢的问题,关于书中的例子(第一个):
1.这部分的意思是什么?如果属性不等于值,检查它的意义是什么(在属性的Set部分,你检查类的字段是否不等于值时,从来没有见过这样的情况)?即使没有它,代码也能工作。书中没有对它的解释。下面是有问题的代码:
如果您有一个值,那么您就可以设置一个值。OnPropertyChanged(“制造”);}
1.为什么如果你在上面提到的一小段代码(第一段)中写属性的名称时没有括号,那么它就不起作用呢?我的意思是如果你写Make,那么它就不起作用,你应该写“Make”。
提前感谢!

rur96b6h

rur96b6h1#

INotifyPropertyChanged是一个接口,它只需要一个类来实现它的需求。它基本上“只是”一个事件。

public interface INotifyPropertyChanged
{
    //
    // Summary:
    //     Occurs when a property value changes.
    event PropertyChangedEventHandler? PropertyChanged;
}

方法OnPropertyChanged只是引发此事件的“帮助程序”。但此处重要的部分是INotifyPropertyChanged实现本身不执行任何操作。
但是WinForms和WPF会自动在一些更高层次的类中订阅此类事件。它们通过反射来标识此类,并将自己添加到PropertyChanged事件的订阅者之一-您的类仍然需要引发该事件。
为此,您需要覆盖属性的Setter:

public string Examle
{
    get { return _field; }
    set
    { 
        _field = value;
        OnPropertyChanged(); // <---
    }
}

否则订阅者将永远不会得到通知。然而,这样做的全部目的是将更改的处理与“模型”分离,这样您的类就充当了数据的容器-因为容器可以传递给不同的窗体,窗体可能希望以不同的方式处理此容器。
检查值是否实际更改只是您可以做的事情-防止通知订阅者(比如说您的数据网格)重新运行您为此类属性设置的所有验证。为了避免不必要的此类验证运行,这将防止通知订阅者此类更改,其中值被重新分配,但它保持不变。
然而,订阅者的自动连接对于作为开发人员的您来说是抽象的,这可能会给初学者带来理解上的困难-然而,从长远来看,这会使您的生活更加容易,因为您可以依赖框架本身(较少用于实现、测试等),所以使用INotifyPropertyChange接口只是一个“约定”,但基本的构建块是通用的C#构造。
希望这能帮上一点忙:)

m0rkklqb

m0rkklqb2#

以下是如何正确实现IPropertyChanged接口的基本示例,该接口通知视图在属性值更改时重新绘制。

public class PropertyChangedEventStackOverflow : INotifyPropertyChanged
{
    //We have a private property of the same public property. This allows us to store the privous value of the property which we can 
    //get at any time when we ask.
    string name = string.Empty;
    //Public property is required to do two-way-binding or one-way-binding from model to the view.
    public string Name
    {
        get => name;
        set
        {
            if (name == value) //Check if the private property is already equals to the exact same value to avoid extra memory 
                return;        //and uneccasary screen re-drawing. So we just return and avoid calling the OnPropertyChanged.
            name = value;      //If the value is different meaning we want to notify view and tell it to re-draw the screen for us.
            OnPropertyChanged(nameof(Name));
        }
    }

    //We create this helper metod to avoid doing something like this every single time we want to notify
    string surname = string.Empty;
    public string Surname
    {
        get => surname;
        set
        {
            if (surname == value)
                return;
            surname = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Surname)));
        }
    }

    //Here is the method to create for OnPropertyChangedEvent
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

如果你想更进一步,并希望在你的属性改变时有一个操作,你可以这样做。backingStore type T = private property

public bool SetProperty<T>(ref T backingStore, T value,
    [CallerMemberName] string propertyName = "",
    Action onChanged = null)
{
    if (EqualityComparer<T>.Default.Equals(backingStore, value))
        return false;

    backingStore = value;
    onChanged?.Invoke();
    OnPropertyChanged(propertyName);
    return true;
}

您可以为它指定一个在属性更改后执行的操作。

23c0lvtd

23c0lvtd3#

1.由于引发PropertyChanged事件可能会对性能造成很大影响(例如,当复杂视图必须重绘自身以显示最新数据时),因此建议仅在必要时引发该事件。
只有当新值发生变化时才需要更新绑定。在新值和旧值相同时更新绑定是多余的,而且可能会导致开销增加。
下面的示例是一个非常详细的版本,用于突出显示正在发生的事情:

private string myProperty;
public string MyProperty
{
  get => this.myProperty;
  set
  {
    string newValue = value;
    string oldValue = this.MyProperty;
    if (newValue != oldValue)
    {
      this.myProperty = newValue;
      OnPropertyChanged(nameof(this.MyProperty));
    }
  }
}

出于性能原因,将空字符串(string.Empty"")或null(而不是属性名称)传递给PropertyChangedEventArgs示例将指示绑定引擎更新引发PropertyChanged事件的类的 * 所有 * 绑定。
换句话说,这就像同时为所有属性引发PropertyChanged事件。
这就是为什么只有在明确需要此行为时才执行此操作(例如,重置和清除所有属性时):

/* Will raise the PropertyChanged event for ALL properties defined by the declaring type */

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(""));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));

/* Will raise the PropertyChanged event ONLY for the particular property that matches the provided property name */

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyProperty"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.MyProperty)));
  1. "Make"被称为字符串文字。编译器将双引号中的每个文字解释为纯文本(字符串文字)。如果不是双引号,编译器将认为该值是语言对象(例如变量或类型)。
    如果您检查PropertyChangedEventArgs的建构函式签章,就会发现它预期属性名称为string
    使用Make(不带双引号)是一个简单的值引用(局部变量或成员变量),它返回Make属性的 value,而不是属性(成员名称)的 name
    需要属性名称来告诉绑定引擎哪个属性已更改,因此需要更新哪个Binding
    下列范例假设属性Make设定为值"BMW"
public string Make
{
  get => this.make; 
  set
  { 
    // For example 'value' is "BMW"
    this.make = value;

    // Invocation a)  
    // Pass the property name as argument.    
    OnPropertyChanged("Make"); // Same as: OnPropertyChanged(nameof(this.Make))

    // Invocation b)
    // Pass the property value as argument
    OnPropertyChanged(Make); 

    // Verbose version of b)
    string modelMake = Make;
    OnPropertyChanged(modelMake);     
  }
}

版本B)不起作用,因为绑定引擎需要属性的成员名称,而不是属性值。
请参阅Microsoft文档:字符串和字符串文字以了解字符串。
请参见INotifyPropertyChanged以了解实现该接口的推荐模式

备注

使用if陈述式 * 和 * Null条件运算子?.?[]检查null的变数(例如事件委派)是多馀的。
在高性能代码中,您可能希望避免冗余的双重检查。
以下版本相同:

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

private void OnPropertyChanged()
{
  PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
} 

private void OnPropertyChanged(string propertyName) => PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
58wvjzkj

58wvjzkj4#

如果属性不等于值,检查它有什么意义
它只是为了避免不必要的更新。2它并不是严格要求的。3每当你有事件通知某个东西被更新的时候,检查这个东西是否真的被改变了通常是一个好主意,因为你不知道什么在监听这个事件,它可能潜在地触发一些缓慢的计算。
为什么如果你在上面提到的一小段代码(在第一分段中)中写的属性名称没有括号,那么它就不能工作呢?
因为事件需要一个字符串,你可以让编译器为你插入这个字符串,方法是写OnPropertyChanged(nameof(Make));。你也可以使用CallerMemberName属性让编译器自动插入这个字符串,只要在你的属性中调用OnPropertyChanged()

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

更进一步,您可以创建一个同时执行比较、字段更新和事件的方法,从而可以编写一行setter => Set(ref _make, value);

private void Set<T>(ref T field, T value, [CallerMemberName] string caller = "")
{
    if (!EqualityComparer<T>.Default.Equals(field, value))
    {
        field = value;
        OnPropertyChanged(caller);
    }
}

但是,我倾向于将所有这些都 Package 在一个单独的类中:

public class Changeable<T> : INotifyPropertyChanged
    {
        private T currentValue;
        public Changeable(T initialValue) => currentValue = initialValue;
        public T Value
        {
            get => currentValue;
            set
            {
                if (!EqualityComparer<T>.Default.Equals(currentValue, value))
                {
                    this.currentValue = value;
                    OnPropertyChanged();
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

这样,您就可以声明一个类似public Changeable<string> Make {get;} = new ("");的属性,并像{Binding Make.Value}那样绑定到它

相关问题