C# ObservableCollection 还监视集合中元素的变化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/269073/
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
ObservableCollection that also monitors changes on the elements in collection
提问by soren.enemaerke
Is there a collection (BCL or other) that has the following characteristics:
是否存在具有以下特征的集合(BCL 或其他):
Sends event if collection is changed AND sends event if any of the elements in the collection sends a PropertyChanged
event. Sort of an ObservableCollection<T>
where T: INotifyPropertyChanged
and the collection is also monitoring the elements for changes.
如果集合发生更改,则发送事件,如果集合中的任何元素发送事件,则发送PropertyChanged
事件。有点像ObservableCollection<T>
whereT: INotifyPropertyChanged
和集合也在监视元素的变化。
I could wrap an observable collection my self and do the event subscribe/unsubscribe when elements in the collection are added/removed but I was just wondering if any existing collections did this already?
我可以自己包装一个可观察的集合,并在添加/删除集合中的元素时执行事件订阅/取消订阅,但我只是想知道是否有任何现有集合已经这样做了?
采纳答案by soren.enemaerke
Made a quick implementation myself:
自己做了一个快速的实现:
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
Unsubscribe(e.OldItems);
Subscribe(e.NewItems);
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
foreach(T element in this)
element.PropertyChanged -= ContainedElementChanged;
base.ClearItems();
}
private void Subscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged += ContainedElementChanged;
}
}
private void Unsubscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged -= ContainedElementChanged;
}
}
private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
}
}
Admitted, it would be kind of confusing and misleading to have the PropertyChanged fire on the collection when the property that actually changed is on a contained element, but it would fit my specific purpose. It could be extended with a new event that is fired instead inside ContainerElementChanged
承认,当实际更改的属性位于包含的元素上时,在集合上触发 PropertyChanged 会有点混乱和误导,但它符合我的特定目的。它可以使用在 ContainerElementChanged 中触发的新事件进行扩展
Thoughts?
想法?
EDIT: Should note that the BCL ObservableCollection only exposes the INotifyPropertyChanged interface through an explicit implementation so you would need to provide a cast in order to attach to the event like so:
编辑:应注意 BCL ObservableCollection 仅通过显式实现公开 INotifyPropertyChanged 接口,因此您需要提供强制转换才能附加到事件,如下所示:
ObservableCollectionEx<Element> collection = new ObservableCollectionEx<Element>();
((INotifyPropertyChanged)collection).PropertyChanged += (x,y) => ReactToChange();
EDIT2: Added handling of ClearItems, thanks Josh
EDIT2:增加了对 ClearItems 的处理,感谢 Josh
EDIT3: Added a correct unsubscribe for PropertyChanged, thanks Mark
EDIT3:为 PropertyChanged 添加了正确的取消订阅,谢谢 Mark
EDIT4: Wow, this is really learn-as-you-go :). KP noted that the event was fired with the collection as sender and not with the element when the a contained element changes. He suggested declaring a PropertyChanged event on the class marked with new. This would have a few issues which I'll try to illustrate with the sample below:
EDIT4:哇,这真的是边走边学:)。KP 指出,当 a 包含的元素发生更改时,该事件是在集合作为发送方而不是元素的情况下触发的。他建议在标有new的类上声明一个 PropertyChanged 事件。这将有一些问题,我将尝试用下面的示例来说明:
// work on original instance
ObservableCollection<TestObject> col = new ObservableCollectionEx<TestObject>();
((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };
var test = new TestObject();
col.Add(test); // no event raised
test.Info = "NewValue"; //Info property changed raised
// working on explicit instance
ObservableCollectionEx<TestObject> col = new ObservableCollectionEx<TestObject>();
col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };
var test = new TestObject();
col.Add(test); // Count and Item [] property changed raised
test.Info = "NewValue"; //no event raised
You can see from the sample that 'overriding' the event has the side effect that you need to be extremely careful of which type of variable you use when subscribing to the event since that dictates which events you receive.
您可以从示例中看到,“覆盖”事件具有副作用,即您在订阅事件时需要非常小心使用哪种类型的变量,因为这决定了您接收哪些事件。
回答by Marcus Griep
Check out the C5 Generic Collection Library. All of its collections contain events that you can use to attach callbacks for when items are added, removed, inserted, cleared, or when the collection changes.
查看C5 通用集合库。它的所有集合都包含事件,您可以使用这些事件在添加、删除、插入、清除项目或集合更改时附加回调。
I am working for some extensions to that libary herethat in the near future should allow for "preview" events that could allow you to cancel an add or change.
我工作的一些扩展到libary这里,在不久的将来,应该允许进行“预览”的事件,可能让你取消添加或更改。
回答by Todd White
If you want to use something built into the framework you can use FreezableCollection. Then you will want to listen to the Changed event.
如果你想使用框架中内置的东西,你可以使用FreezableCollection。然后,您将想要收听Changed 事件。
Occurs when the Freezable or an object it contains is modified.
当 Freezable 或其包含的对象被修改时发生。
Here is a small sample. The collection_Changed method will get called twice.
这是一个小样本。collection_Changed 方法将被调用两次。
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
FreezableCollection<SolidColorBrush> collection = new FreezableCollection<SolidColorBrush>();
collection.Changed += collection_Changed;
SolidColorBrush brush = new SolidColorBrush(Colors.Red);
collection.Add(brush);
brush.Color = Colors.Blue;
}
private void collection_Changed(object sender, EventArgs e)
{
}
}
回答by Mark Whitfeld
@soren.enemaerke: I would have made this comment on your answer post, but I can't (I don't know why, maybe because I don't have many rep points). Anyway, I just thought that I'd mention that in your code you posted I don't think that the Unsubscribe would work correctly because it is creating a new lambda inline and then trying to remove the event handler for it.
@soren.enemaerke:我会在你的回答帖子上发表这个评论,但我不能(我不知道为什么,也许是因为我没有很多代表点)。无论如何,我只是想我会在您发布的代码中提到我认为取消订阅不会正常工作,因为它正在创建一个新的内联 lambda,然后尝试为其删除事件处理程序。
I would change the add/remove event handler lines to something like:
我会将添加/删除事件处理程序行更改为:
element.PropertyChanged += ContainedElementChanged;
and
和
element.PropertyChanged -= ContainedElementChanged;
And then change the ContainedElementChanged method signature to:
然后将 ContainedElementChanged 方法签名更改为:
private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
This would recognise that the remove is for the same handler as the add and then remove it correctly. Hope this helps somebody :)
这将识别删除与添加相同的处理程序,然后正确删除它。希望这对某人有所帮助:)
回答by Lukas Cenovsky
I would use ReactiveUI'sReactiveCollection
:
我会使用ReactiveUI 的ReactiveCollection
:
reactiveCollection.Changed.Subscribe(_ => ...);
回答by Ovi
@soren.enemaerke Made this a reply in order to post proper code as the comments section on your answer would render it unreadable.
The only issue I've had with the solution is that the particular element which triggers the PropertyChanged
event is lost and you have no way of knowing in the PropertyChanged
call.
@soren.enemaerke 将此回复以发布正确的代码,因为您的答案的评论部分会使其不可读。我在解决方案中遇到的唯一问题是触发PropertyChanged
事件的特定元素丢失了,您无法在PropertyChanged
调用中知道。
col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName)
To fix this I've created a new class PropertyChangedEventArgsEx
and changed the ContainedElementChanged
method within your class.
为了解决这个问题,我创建了一个新类PropertyChangedEventArgsEx
并更改了类中的ContainedElementChanged
方法。
new class
新班级
public class PropertyChangedEventArgsEx : PropertyChangedEventArgs
{
public object Sender { get; private set; }
public PropertyChangedEventArgsEx(string propertyName, object sender)
: base(propertyName)
{
this.Sender = sender;
}
}
changes to your class
改变你的班级
private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
{
var ex = new PropertyChangedEventArgsEx(e.PropertyName, sender);
OnPropertyChanged(ex);
}
After this you can get the actual Sender
element in col.PropertyChanged += (s, e)
by casting e
to PropertyChangedEventArgsEx
在此之后,您可以通过强制转换来获取实际Sender
元素col.PropertyChanged += (s, e)
e
PropertyChangedEventArgsEx
((INotifyPropertyChanged)col).PropertyChanged += (s, e) =>
{
var argsEx = (PropertyChangedEventArgsEx)e;
Trace.WriteLine(argsEx.Sender.ToString());
};
Again, you should note the the s
here is the collection of elements, not the actual element that triggered the event. Hence the new Sender
property in the PropertyChangedEventArgsEx
class.
同样,您应该注意s
这里是元素的集合,而不是触发事件的实际元素。因此Sender
,PropertyChangedEventArgsEx
类中的新属性。
回答by Dave Sexton
Rxx 2.0 contains operatorsthat along with this conversion operatorfor ObservableCollection<T>
makes it easy to achieve your goal.
Rxx 2.0 包含的操作符与这个转换操作符一起ObservableCollection<T>
可以轻松实现您的目标。
ObservableCollection<MyClass> collection = ...;
var changes = collection.AsCollectionNotifications<MyClass>();
var itemChanges = changes.PropertyChanges();
var deepItemChanges = changes.PropertyChanges(
item => item.ChildItems.AsCollectionNotifications<MyChildClass>());
The following property changed notification patterns are supported for MyClass
and MyChildClass
:
MyClass
和支持以下属性更改通知模式MyChildClass
:
- INotifyPropertyChanged
- [Property]Changed event pattern (legacy, for use by Component Model)
- WPF dependency properties
- INotifyPropertyChanged
- [属性]更改的事件模式(遗留,供组件模型使用)
- WPF 依赖属性
回答by Scott Chamberlain
The simplest way to do it is just do
最简单的方法就是做
using System.ComponentModel;
public class Example
{
BindingList<Foo> _collection;
public Example()
{
_collection = new BindingList<Foo>();
_collection.ListChanged += Collection_ListChanged;
}
void Collection_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show(e.ListChangedType.ToString());
}
}
The BindingListclass as been in .net sence 2.0. It will fire it's ListChanged
event any time a item in the collection fires INotifyPropertyChanged
.
该的BindingList如已经在.NET 2.0 SENCE类。ListChanged
每当集合中的项目触发时,它就会触发它的事件INotifyPropertyChanged
。