INotifyPropertyChanged 不适用于 ObservableCollection<Class> 中的类

Posted

技术标签:

【中文标题】INotifyPropertyChanged 不适用于 ObservableCollection<Class> 中的类【英文标题】:INotifyPropertyChanged does not work from class held in ObservableCollection<Class> 【发布时间】:2021-05-15 02:39:36 【问题描述】:

我有一个类“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&lt;BaseClass&gt;

DI类:

    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 会正确“捕获”事件。但是,当我更新ObservableCollection 中的IsOn 属性时,如下所示,ViewModel 中没有拾取该事件。

ClassesOfA[0].IsOn = true;

为什么 INPC 接口不能与嵌套属性一起使用? here的问答似乎很相关,但我想不通。

编辑:附加说明和代码:

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

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

但是,这仍然不能通知我的ViewModel,我的DIClassObservableCollection&lt;BaseClass&gt; 的属性已更改。我想使用 INPC 通过 MVVM 层向上冒泡事件信息/属性更新。但是我想“包装”它们以使我的类更干净/更少的属性

编辑:

我添加了我的问题/场景的这个“草图”,并带有基本命名以使其简单:

【问题讨论】:

BaseClass 的 PropertyChanged 事件不会传播到 DIClass。您必须在 ClassesOfA 中注册每个项目才能收到有关其更改的通知。可观察的集合并不意味着可以观察到其项目的变化,但可以观察到集合的(添加、删除、...) 嗨@Fildor,我应该在我的ViewModel 中注册ClassesOfA 的每一项吗?类似DIClass.ClassesOfA[0].PropertyChanged =+ ... ? 如果你需要的话。但我其实对此表示怀疑。为什么你的 ViewModel 需要观察这个? 我为DIClass中的每个项目注册了这样的:ClassesOfA[0].PropertyChanged += DIClass_PropertyChanged;,当IsOn更新时调用此属性更改方法。但我需要我的 ViewModel 知道 IsOn 何时更改,以便更新我的 UI。 【参考方案1】:

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

ObservableCollection 有两个事件:

CollectionChanged:当集合改变时触发,例如collection.Add( item ) PropertyChanged:在属性更改时触发,例如collection = new ObservablecCollection&lt;T&gt;();

我认为您不需要 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,我会提供一个 ItemTemplate,BaseClass 项在其中绑定。这样做可以避免注册到每个项目的 PropertyChanged。

【讨论】:

谢谢,我确实将我的 UI 绑定到 observableCollection。但是我的问题仍然存在 - 实际上,有没有一种方法可以使用 INPC 来“包装”一组属性。因此,我希望能够使用 INPC 在 MVVM 中向上通信层,但我不希望在我的类中存在 10 多个实例(它们包含属性),这会变得笨重。如果可能的话,我想将它们“包装”在一个集合中【参考方案2】:

我的建议是,您可以创建一个自定义的 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, sender, IndexOf((T)sender));
        OnCollectionChanged(args);
    

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

更新:

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

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

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

DIClass 对象中处理BaseClass 的属性更改事件是做你想做的事情的正确方法。就像在 ViewModel 中处理 DIClass 事件一样。但你不想要它,因为可能有很多对象。

那么您的示例的第一个版本是通过您自己触发DIClass 的属性更改事件来实现您想要的推荐方式。

【讨论】:

谢谢@Roy Li,我按原样实现了你的代码,虽然它不起作用 我用你发布的代码测试了这个类。它运作良好。您能否创建一个空白的 UWP 应用程序?然后添加您在问题中发布的代码和我发布的课程。如果您能重现该问题,请在 Github 或 OneDrive 上与我分享minimal reproducible example。 嗨 Roy,here 是我的示例项目,所有代码都在讨论中。感谢帮助 刚刚试用了您的样品。目前尚不清楚您提到的意外行为是什么。你的意思是切换按钮onetwo 在单击stopall 按钮时不会改变?同样,一个最小的样本将有助于找出问题。 我正忙于创建一个最小的控制台示例。是的,当单击 stopall 时,我希望 ViewModel 中的 OptionRules_Shell__PropertyChanged 方法“触发”/运行,设置 onetwo

以上是关于INotifyPropertyChanged 不适用于 ObservableCollection<Class> 中的类的主要内容,如果未能解决你的问题,请参考以下文章

使用属性... INotifyPropertyChanged

INotifyPropertyChanged WPF

INotifyPropertyChanged 订阅自我

INotifyPropertyChanged的作用

INotifyPropertyChanged WPF

INotifyPropertyChanged 和 DependencyProperty 有啥关系?