wpf 在 ObservableCollection 上触发 InotifyPropertyChanged/CollectionChanged
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30444874/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Trigger InotifyPropertyChanged/CollectionChanged on ObservableCollection
提问by Select Case Beer
I've tried looking at other topics on this but I haven't found a working implementation to my question. Basically, I have an ObservableCollection called "FruitBasket" that contains different kinds of fruit. FruitBasket itself contains ObservableCollectionsfor each respective type of fruit that passes through so that they can be used as ItemSourcesfor ListViews(Denoted by their names "AppleContainer" and "OrangeContainer"), each displaying one kind of fruit. Because the fruit classes themselves implement INotifyPropertyChanged, modifying their values triggers updates to the ListView controls just fine, however, FruitBasket has a "TotalWeight" property derived from the weights of all the other fruits in the collections. I want "TotalWeight" to update the Labelcontrol in the UI without me having to refresh the UI. Triggerering a notification on a property change of the actual ObservableCollectionitself, and not simply its constituent members is more difficult and I haven't found any solutions that work so far (or that I've implemented correctly).
我已经尝试查看有关此的其他主题,但我还没有找到我的问题的有效实现。基本上,我有一个名为“FruitBasket”的 ObservableCollection,其中包含不同种类的水果。水果篮本身包含ObservableCollections为穿过,使得它们可被用作每个相应类型的水果ItemSources为列表视图(由它们的名字“AppleContainer”和“OrangeContainer”表示),每个展示一种水果。因为水果类本身实现了 INotifyPropertyChanged,修改它们的值会触发对 ListView 控件的更新,但是,FruitBasket 有一个从集合中所有其他水果的权重派生的“TotalWeight”属性。我希望“TotalWeight”在无需刷新 UI 的情况下更新UI 中的Label控件。触发关于实际ObservableCollection本身的属性更改的通知,而不仅仅是它的组成成员,这更困难,而且到目前为止我还没有找到任何有效的解决方案(或者我已经正确实施)。
public class FruitBasket : ObservableCollection<IFruit>
{
private decimal _totalWeight;
public FruitBasket()
{
this.Add(new OrangeContainer(this));
this.Add(new AppleContainer(this));
}
public OrangeContainer Oranges
{
get { return (OrangeContainer)this.Items[0]; }
}
public AppleContainer Apples
{
get { return (AppleContainer)this.Items[1]; }
}
public decimal TotalWeight
{
get { return _totalWeight; }
set { _totalWeight = value; }
}
internal void UpdateWeight(IFruit caller)
{
_totalWeight = 0;
foreach (Orange orng in (OrangeContainer)this.Items[0])
{
_totalWeight += orng.Weight;
}
foreach (Apple appl in (AppleContainer)this.Items[1])
{
_totalWeight += appl.Weight;
}
}
回答by Liero
You need to call INotifyPropertyChanged.PropertyChanged event of your FruitBasket whenever items are added, removed or Weight property of any item has changed.
每当添加、删除项目或更改任何项目的 Weight 属性时,您都需要调用 FruitBasket 的 INotifyPropertyChanged.PropertyChanged 事件。
Let's split it into two tasks:
让我们把它分成两个任务:
- TotalWeight should be recalculated when items are added, removed, or items' weight is changed. We need to handle those events.
- Raise FruitBasket.PropertyChanged event
- 当添加、删除项目或更改项目的重量时,应重新计算 TotalWeight。我们需要处理这些事件。
- 引发 FruitBasket.PropertyChanged 事件
I have splitted these two tasks into two classes in order to follow Single Responsibility Principle:
为了遵循单一职责原则,我将这两项任务分为两类:
1) - this handles items' PropertyChanged events:
1) - 这处理项目的 PropertyChanged 事件:
public abstract class ExtendedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void ClearItems()
{
foreach (var item in Items) item.PropertyChanged -= ItemPropertyChanged;
base.ClearItems();
}
protected override void InsertItem(int index, T item)
{
item.PropertyChanged += ItemPropertyChanged;
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
this[index].PropertyChanged -= ItemPropertyChanged;
base.RemoveItem(index);
}
protected override void SetItem(int index, T item)
{
this[index].PropertyChanged -= ItemPropertyChanged;
item.PropertyChanged += ItemPropertyChanged;
base.SetItem(index, item);
}
abstract void ItemPropertyChanged(object sender, PropertyChangedEventArgs e);
}
2) - this recalculates TotalWeight when necessary
2) - 必要时重新计算 TotalWeight
public class FruitBasket : ExtendedObservableCollection<IFruit>
{
protected override void ItemPropertyChanged(object sender, PropertyChangedEventArgs e){
UpdateWeight();
OnPropertyChanged("TotalWeight")
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
UpdateWeight();
OnPropertyChanged("TotalWeight")
base.OnCollectionChanged(e);
}
}
Of course your Fruit should implement INotifyPropertyChanged interface. You will find plenty of examples how to do it. It is very simple.
当然你的 Fruit 应该实现 INotifyPropertyChanged 接口。你会发现很多如何做到这一点的例子。这很简单。
回答by Select Case Beer
I found the root(s) of the problem(s). I'll start with the most obvious:
我找到了问题的根源。我将从最明显的开始:
I wasn't as diligent in assigning datacontext in the UI for the Fruit Basket observable collection object itself as I was the for the members of its collection (OrangeContainer and AppleContainer). In the initialization of the UI window, assigning datacontext to the ListView objects is second nature. I wasn't quite matching the right node's datacontext in the XAML to the Fruit Basket object in the initialization method in the code behind (I really should have checked that earlier).
我在为 Fruit Basket 可观察集合对象本身分配 UI 中的数据上下文时并不像为它的集合(OrangeContainer 和 AppleContainer)的成员分配数据上下文那样勤奋。在 UI 窗口的初始化中,将数据上下文分配给 ListView 对象是第二天性。我没有将 XAML 中正确节点的数据上下文与后面代码中初始化方法中的 Fruit Basket 对象完全匹配(我真的应该早点检查)。
Because of the misaligned assignments of datacontext/binding, between the XAML and initialization method, the propertychanged event was never firing for my fruit basket observable collection like it was for the Apple and Orange objects inside the OrangeContainer and AppleContainer collections that were members of FruitBasket. So, in the Orange class declaration we'd have this:
由于数据上下文/绑定的未对齐分配,在 XAML 和初始化方法之间,propertychanged 事件从未为我的水果篮可观察集合触发,就像为作为 FruitBasket 成员的 OrangeContainer 和 AppleContainer 集合中的 Apple 和 Orange 对象触发一样。所以,在 Orange 类声明中,我们有这样的:
public class Orange : INotifyPropertyChanged, IFruit
And the implementation like so
和这样的实现
public event PropertyChangedEventHandler PropertyChanged;
public void PropChange(string prop)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
And when the PropChangemethod was called in the Weightproperty setter, this.PropertyChangedwould not be null, and everything would work fine.
而当属性改变方法在被称为重属性setter,this.PropertyChanged就不会是空,一切都将正常工作。
The FruitBasket class was a bit more tricky. Because of the aformentioned issue of improper matching in the UI Code, this.PropertyChangedwould return nullevery time I tried to notify a change in property. However, it got a bit more confusing because unlike the Orangeor Appleclasses, it inherits ObservableCollection (ObservableCollection in the declaration if we want to be specific). I know ObservableCollectionis really just a Collectionclass that implements INotifyPropertyChanged and INotifyCollectionChanged interfaces. It's really nice to see the plumbing now that .NET is open source (praise the lord)
FruitBasket 类有点棘手。由于上述 UI 代码中不正确匹配的问题,每次我尝试通知属性更改时,this.PropertyChanged都会返回null。然而,它有点令人困惑,因为与Orange或Apple类不同,它继承了 ObservableCollection(如果我们想要具体,则在声明中为 ObservableCollection)。我知道ObservableCollection实际上只是一个实现 INotifyPropertyChanged 和 INotifyCollectionChanged 接口的Collection类。现在 .NET 是开源的,很高兴看到管道(赞美主)
In any case, implementing this became more confusing, because I kept seeing this:
无论如何,实现这一点变得更加混乱,因为我一直看到这个:
Warning 1 'TestingObsColNotify.FruitBasket.PropertyChanged' hides inherited member 'System.Collections.ObjectModel.ObservableCollection.PropertyChanged'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. C:\Testing VS Project\TestingObsColNotify\TestingObsColNotify\FruitBasket.cs 60 50 TestingObsColNotify
警告 1 'TestingObsColNotify.FruitBasket.PropertyChanged' 隐藏继承的成员 'System.Collections.ObjectModel.ObservableCollection.PropertyChanged'。要使当前成员覆盖该实现,请添加 override 关键字。否则添加新关键字。C:\Testing VS Project\TestingObsColNotify\TestingObsColNotify\FruitBasket.cs 60 50 TestingObsColNotify
I still see this, but my implementation works, because while it was a result of the inheritance from INotifyProperty changed via ObservableCollection as seen in my original class declaration
我仍然看到这一点,但我的实现有效,因为虽然它是从 INotifyProperty 的继承通过 ObservableCollection 更改的结果,如我原来的类声明中所见
public class FruitBasket : ObservableCollection<IFruit>
This was just shy of the last element need to make everything work, which was adding the INotifyPropertyChanged to the class itself like so:
这只是使一切正常工作所需的最后一个元素,即将 INotifyPropertyChanged 添加到类本身,如下所示:
public class FruitBasket : ObservableCollection<IFruit>, INotifyPropertyChanged
It seems a bit redundant and inelegant but I didn't get very far trying to override and wrestle with the inheritance of INotifyPropertyChanged from ObservableCollection (Or as best as I can understand).
这似乎有点多余和不雅,但我并没有尝试覆盖和解决来自 ObservableCollection 的 INotifyPropertyChanged 的继承(或者尽我所能理解)。
So there we have it, everything works now, sans MVVM. I'll certainly move on to that pattern later, but it's nice to have resolved this issue instead of lazily just re-assigning the contents of controls in the code behind methods on the UI side of things.
所以我们有了它,现在一切正常,没有 MVVM。稍后我肯定会继续使用该模式,但很高兴解决了这个问题,而不是懒洋洋地在 UI 方面的方法背后的代码中重新分配控件的内容。
Thank you to those who came in here and contributed, I appreciate you taking the time to respond.
感谢那些来到这里并做出贡献的人,感谢您抽出时间来回复。
回答by Contango
If using binding, Add the interface INotifyPropertyChangedto your class. If you have ReSharper installed, accept the recommendation to implement the interface. Then, whenever you want to update any text box, call PropertyChangedwith the name of the property TotalWeight, see https://softwareengineering.stackexchange.com/questions/228067/where-do-put-inotifypropertychanged-interface-in-model-or-viewmodel. Whenever you update any of the ObservableCollections, manually update the TotalWeight, then call the aforementioned PropertyChangedto tell the UI to update itself. I've used this technique to push updates from the ViewModel into the View (i.e. from the class into the XAML) for some fairly complex scenarios, it works very well.
如果使用绑定,请将接口添加INotifyPropertyChanged到您的类中。如果您安装了 ReSharper,请接受实施该接口的建议。然后,每当您想更新任何文本框时,请PropertyChanged使用属性名称调用TotalWeight,请参阅https://softwareengineering.stackexchange.com/questions/228067/where-do-put-inotifypropertychanged-interface-in-model-or-视图模型。每当您更新任何ObservableCollections,手动更新TotalWeight,然后调用上述PropertyChanged通知 UI 更新自身。对于一些相当复杂的场景,我已经使用这种技术将更新从 ViewModel 推送到 View(即从类到 XAML),它工作得非常好。
I'd also recommend following the learning curve for MVVM, projects written in that way tend to be more scalable, are easier to maintain, and just easier to work with.
我还建议遵循 MVVM 的学习曲线,以这种方式编写的项目往往更具可扩展性,更易于维护,并且更易于使用。

