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<BaseClass>
:
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
,我的DIClass
的ObservableCollection<BaseClass>
的属性已更改。我想使用 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<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,我会提供一个 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。
如果我正确理解您的场景,您希望在 BaseClass
object 中的相同属性发生更改时更改 ToggleButton 的状态。但是 ToggleButtons 绑定到 VMData
对象,因此当 BaseClass
对象在 DIClass
对象中发生更改时,您需要得到通知。所以你希望BaseCasss
的属性改变事件触发DIClass
的属性改变事件。
在DIClass
对象中处理BaseClass
的属性更改事件是做你想做的事情的正确方法。就像在 ViewModel 中处理 DIClass
事件一样。但你不想要它,因为可能有很多对象。
那么您的示例的第一个版本是通过您自己触发DIClass
的属性更改事件来实现您想要的推荐方式。
【讨论】:
谢谢@Roy Li,我按原样实现了你的代码,虽然它不起作用 我用你发布的代码测试了这个类。它运作良好。您能否创建一个空白的 UWP 应用程序?然后添加您在问题中发布的代码和我发布的课程。如果您能重现该问题,请在 Github 或 OneDrive 上与我分享minimal reproducible example。 嗨 Roy,here 是我的示例项目,所有代码都在讨论中。感谢帮助 刚刚试用了您的样品。目前尚不清楚您提到的意外行为是什么。你的意思是切换按钮one
和two
在单击stopall
按钮时不会改变?同样,一个最小的样本将有助于找出问题。
我正忙于创建一个最小的控制台示例。是的,当单击 stopall 时,我希望 ViewModel
中的 OptionRules_Shell__PropertyChanged
方法“触发”/运行,设置 one
和 two
以上是关于INotifyPropertyChanged 不适用于 ObservableCollection<Class> 中的类的主要内容,如果未能解决你的问题,请参考以下文章