INotifyPropertyChanged 不适用于 ObservableCollection<Class>

问题描述

我有一个类,“BaseClass”,它实现了 INotifyPropertyChanged 并具有以下内容

基类:

    private bool isOn;
    public bool IsOn
    {
        get { return isOn; }
        set
        {
            isOn = value;
            RaisePropertyChanged("BaseClass:IsOn");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this,new PropertyChangedEventArgs(name));
        }
    }

然后我有一个类,“DIClass”,它也实现了 INotifyPropertyChanged。它还有一个 ObservableCollection<BaseClass>:

DIClass:

    public ObservableCollection<BaseClass> ClassesOfA;

    private string iNPCTest;
    public string INPCTest 
    {
        get { return iNPCTest; }
        set
        {
            iNPCTest = value;
            RaisePropertyChanged("DIClass: INPCTest");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this,new PropertyChangedEventArgs(name));
        }
    }

我的 viewmodel 拥有一个“DIClass”实例并注册到它的 PropertyChanged 事件。当我在“DIClass”中设置 INPCTest 的值时,viewmodel 会正确“捕获”事件。但是,当我更新 IsOn 中的 ObservableCollection 属性时,如下所示,viewmodel 中没有选择该事件。

ClassesOfA[0].IsOn = true;

为什么 INPC 接口不适用于嵌套属性?问题和答案 here 似乎很相关,但我想不通。

编辑:附加说明和代码

我可以注册 PropetyChanged 项的 ObservableCollection 事件,例如:

        ClassesOfA[0].PropertyChanged += DIClass_PropertyChanged;
        ClassesOfA[1].PropertyChanged += DIClass_PropertyChanged;

但是,这仍然没有冒泡通知我的 viewmodel,我的 DIClassObservableCollection<BaseClass>属性已更改。我想使用 INPC 通过 MVVM 层冒泡事件信息/属性更新。但我想“包装”它们以使我的类更清洁/更少的属性

编辑:

添加了我的问题/场景的“草图”,并使用了基本命名以使其变得简单:

enter image description here

解决方法

回答您的问题:这是设计使然。

ObservableCollection 有两个事件:

  • CollectionChanged:当集合改变时触发,例如collection.Add( item )
  • PropertyChanged:当属性改变时触发,例如collection = new ObservablecCollection<T>();

我认为您不需要 ObservableCollection,因为-据我了解您的问题-您想观察集合中项目属性的变化。要实现这一点,您需要像这样注册到每个观察到的项目的 PropertyChanged:

public List<BaseClass> Collection {get;set;}

public void InitializeCollection( IEnumerable<BaseClass> baseClassCollection){
    Collection = new List<BaseClass>();
    foreach(var item in baseClassCollection){
        item.PropertyChanged += MethodToCallOnPropertyChanges;
        Collection.Add( item );
    }
}

public void MethodToCallOnPropertyChanges(object sender,PropertyChangedEventArgs e){
   //react to any property changes
   doSomething();
   //react to specific properties
   if(e != null && e.PropertyName.Equals("isOn"))
       doSomethingOtherStuff();
}

这可能非常烦人,并可能导致一些其他问题。 如果遇到这种情况,我会考虑重新设计 ViewModel 和 UI。我会尝试拥有一个绑定到每个 BaseClass 项目的 UI。例如,如果我有一个 ListView,我将提供一个绑定 BaseClass 项目的 ItemTemplate。这样做将防止需要注册到每个项目的 PropertyChanged。

,

我的建议是,您可以创建一个自定义的 ObservableCollection 类,当列表项上的属性发生更改时,该类会引发重置操作。它强制所有项目实现 INotifyPropertyChanged。

我做了一个简单的演示,你可以查看:

   public class DIClass : INotifyPropertyChanged
  {
    public ExObservableCollection<BaseClass> ClassesOfA
    ... other code...
     
   }


    public sealed class ExObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
    public ExObservableCollection()
    {
        CollectionChanged += AllObservableCollectionCollectionChanged;
    }

    public ExObservableCollection(IEnumerable<T> pItems) : this()
    {
        foreach (var item in pItems)
        {
            this.Add(item);
        }
    }

    private void AllObservableCollectionCollectionChanged(object sender,NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (Object item in e.NewItems)
            {
                ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
            }
        }
    }

    private void ItemPropertyChanged(object sender,PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,sender,IndexOf((T)sender));
        OnCollectionChanged(args);
    }
}

然后您可以在 ExObservableCollection 对象中使用 DIClass 类。当 BaseClass 中的属性发生变化时,UI 将更新。

更新:

最后,我根据复杂的样本发现了您提到的意外行为。 ExObservableCollection 类运行良好并正确触发属性更改事件。

关键是你认为如果 baseclass 中的属性更改事件被触发,那么它会 也触发 DIClass 中的属性更改事件,对吗?我不得不说这是不正确的。属性更改事件仅在当前类中触发。除非您在父类中处理它,否则它不会传递给父类。它只触发一次并在目标属性更改时通知 UI。

如果我正确理解您的情况,您希望在更改 BaseClass 对象中的相同属性时更改 ToggleButton 的状态。但是 ToggleButton 绑定到 VMData 对象,因此您需要在 BaseClass 对象中的 DIClass 对象发生更改时收到通知。所以你想让BaseCasss的属性改变事件触发DIClass的属性改变事件。

处理 BaseClass 对象中 DIClass 的属性更改事件是实现您想要的正确方法。这与在 ViewModel 中处理 DIClass 事件相同。但是你不想要它,因为可能有很多对象。

然后,您的示例的第一个版本是通过自己触发 DIClass 的属性更改事件来实现您想要的目标的推荐方法。