我这里有些东西让我措手不及.
我有一个带有项目的Ob的ObservableCollection.我还有一个附加到CollectionChanged事件的事件处理程序.
当您清除集合,将导致以e.Action的CollectionChanged事件设置为NotifyCollectionChangedAction.Reset.好的,这很正常.但奇怪的是,e.OldItems或e.NewItems都没有任何内容.我希望e.OldItems可以填充从集合中删除的所有项目.
有没有人见过这个?如果是这样,他们是如何绕过它的?
一些背景:我使用CollectionChanged事件来附加和分离另一个事件,因此如果我没有在e.OldItems中获取任何项目......我将无法从该事件中分离出来.
澄清: 我知道文档并没有完全声明它必须以这种方式行事.但对于其他所有行动,它都会告诉我它做了什么.所以,我的假设是它会告诉我......在清除/重置的情况下.
如果您希望自己重现,下面是示例代码.首先关闭xaml:
接下来,代码背后:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Collections.ObjectModel; namespace ObservableCollection { ////// Interaction logic for Window1.xaml /// public partial class Window1 : Window { public Window1() { InitializeComponent(); _integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged); } private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { switch (e.Action) { case System.Collections.Specialized.NotifyCollectionChangedAction.Add: break; case System.Collections.Specialized.NotifyCollectionChangedAction.Move: break; case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: break; case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: break; case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: break; default: break; } } private void addButton_Click(object sender, RoutedEventArgs e) { _integerObservableCollection.Add(25); } private void moveButton_Click(object sender, RoutedEventArgs e) { _integerObservableCollection.Move(0, 19); } private void removeButton_Click(object sender, RoutedEventArgs e) { _integerObservableCollection.RemoveAt(0); } private void replaceButton_Click(object sender, RoutedEventArgs e) { _integerObservableCollection[0] = 50; } private void resetButton_Click(object sender, RoutedEventArgs e) { _integerObservableCollection.Clear(); } private ObservableCollection_integerObservableCollection = new ObservableCollection { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; } }
Orion Edward.. 46
它不声称包含旧项目,因为重置并不意味着列表已被清除
这意味着发生了一些戏剧性的事情,并且计算添加/删除的成本很可能超过仅从头开始重新扫描列表的成本......所以这就是你应该做的.
MSDN建议将整个集合重新排序为重置候选者的示例.
重申一下.重置并不意味着明确,这意味着您对该列表的假设现在无效.把它看作是一个全新的清单.Clear恰好是其中的一个例子,但其他人可能也是如此.
一些例子:
我有一个这样的列表,其中包含很多项目,并且它已被数据绑定到WPF ListView
以显示在屏幕上.
如果你清除列表并提升.Reset
事件,那么性能几乎是即时的,但是如果你提出很多单独的.Remove
事件,性能就会很糟糕,因为WPF会逐个删除这些项目.我还在.Reset
我自己的代码中使用来表明列表已经重新排序,而不是发出数千个单独的Move
操作.与Clear一样,在筹集许多个人活动时会有很大的性能影响.
它不声称包含旧项目,因为重置并不意味着列表已被清除
这意味着发生了一些戏剧性的事情,并且计算添加/删除的成本很可能超过仅从头开始重新扫描列表的成本......所以这就是你应该做的.
MSDN建议将整个集合重新排序为重置候选者的示例.
重申一下.重置并不意味着明确,这意味着您对该列表的假设现在无效.把它看作是一个全新的清单.Clear恰好是其中的一个例子,但其他人可能也是如此.
一些例子:
我有一个这样的列表,其中包含很多项目,并且它已被数据绑定到WPF ListView
以显示在屏幕上.
如果你清除列表并提升.Reset
事件,那么性能几乎是即时的,但是如果你提出很多单独的.Remove
事件,性能就会很糟糕,因为WPF会逐个删除这些项目.我还在.Reset
我自己的代码中使用来表明列表已经重新排序,而不是发出数千个单独的Move
操作.与Clear一样,在筹集许多个人活动时会有很大的性能影响.
我们在这里遇到了同样的问题.CollectionChanged中的Reset操作不包含OldItems.我们有一个解决方法:我们使用以下扩展方法:
public static void RemoveAll(this IList list) { while (list.Count > 0) { list.RemoveAt(list.Count - 1); } }
我们最终不支持Clear()函数,并在CollectionChanged事件中为Reset操作抛出NotSupportedException.RemoveAll将使用正确的OldItems在CollectionChanged事件中触发Remove操作.
另一个选项是使用单个Remove事件替换Reset事件,该事件在其OldItems属性中包含所有已清除的项,如下所示:
public class ObservableCollectionNoReset: ObservableCollection { protected override void ClearItems() { List removed = new List (this); base.ClearItems(); base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Reset) base.OnCollectionChanged(e); } // Constructors omitted ... }
好处:
无需订阅其他活动(按照接受的答案要求)
不为每个删除的对象生成事件(其他一些建议的解决方案会导致多个已删除的事件).
订阅者只需要在任何事件上检查NewItems和OldItems,以根据需要添加/删除事件处理程序.
缺点:
没有重置事件
小(?)开销创建列表副本.
???
编辑2012-02-23
不幸的是,当绑定到基于WPF列表的控件时,使用多个元素清除ObservableCollectionNoReset集合将导致异常"不支持范围操作".要与具有此限制的控件一起使用,我将ObservableCollectionNoReset类更改为:
public class ObservableCollectionNoReset: ObservableCollection { // Some CollectionChanged listeners don't support range actions. public Boolean RangeActionsSupported { get; set; } protected override void ClearItems() { if (RangeActionsSupported) { List removed = new List (this); base.ClearItems(); base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } else { while (Count > 0 ) base.RemoveAt(Count - 1); } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Reset) base.OnCollectionChanged(e); } public ObservableCollectionNoReset(Boolean rangeActionsSupported = false) { RangeActionsSupported = rangeActionsSupported; } // Additional constructors omitted. }
当RangeActionsSupported为false(默认值)时,这不是那么有效,因为集合中的每个对象都会生成一个Remove通知
我找到了一个解决方案,允许用户同时利用添加或删除多个项目的效率,同时只触发一个事件 - 并满足UIElements的需求以获取Action.Reset事件args而所有其他用户将就像添加和删除的元素列表一样.
此解决方案涉及覆盖CollectionChanged事件.当我们开始触发此事件时,我们实际上可以查看每个已注册处理程序的目标并确定其类型.由于只有ICollectionView类NotifyCollectionChangedAction.Reset
在多个项目发生更改时才需要args,因此我们可以将它们单独输出,并为其他人提供包含已删除或添加的完整项目列表的正确事件参数.以下是实施.
public class BaseObservableCollection: ObservableCollection { //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable ) and Clear() private bool _SuppressCollectionChanged = false; /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args. public override event NotifyCollectionChangedEventHandler CollectionChanged; public BaseObservableCollection() : base(){} public BaseObservableCollection(IEnumerable data) : base(data){} #region Event Handlers protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if( !_SuppressCollectionChanged ) { base.OnCollectionChanged(e); if( CollectionChanged != null ) CollectionChanged.Invoke(this, e); } } //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable //for applications in code, so we actually check the type we're notifying on and pass a customized event args. protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler handlers = this.CollectionChanged; if( handlers != null ) foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() ) handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } #endregion #region Extended Collection Methods protected override void ClearItems() { if( this.Count == 0 ) return; List removed = new List (this); _SuppressCollectionChanged = true; base.ClearItems(); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } public void Add(IEnumerable toAdd) { if( this == toAdd ) throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified."); _SuppressCollectionChanged = true; foreach( T item in toAdd ) Add(item); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List (toAdd))); } public void Remove(IEnumerable toRemove) { if( this == toRemove ) throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified."); _SuppressCollectionChanged = true; foreach( T item in toRemove ) Remove(item); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List (toRemove))); } #endregion }
好的,我知道这是一个非常古老的问题,但我已经想出了一个很好的解决方案,并且认为我会分享.这个解决方案从这里的很多很好的答案中获得灵感,但具有以下优点:
无需从ObservableCollection创建新类和覆盖方法
不会篡改NotifyCollectionChanged的工作方式(所以不要乱搞重置)
不利用反射
这是代码:
public static void Clear(this ObservableCollection collection, Action > unhookAction) { unhookAction.Invoke(collection); collection.Clear(); }
这个扩展方法只需要Action
在清除集合之前调用它.
好吧,即使我仍然希望ObservableCollection按照我的意愿行事......下面的代码就是我最终要做的.基本上,我创建了一个名为TrulyObservableCollection的新T集合,并覆盖了ClearItems方法,然后我用它来引发Clearing事件.
在使用此TrulyObservableCollection的代码中,我使用此清除事件来遍历当时仍在集合中的项目,以便对我希望从中分离的事件进行分离.
希望这种方法能够帮助其他人.
public class TrulyObservableCollection: ObservableCollection { public event EventHandler Clearing; protected virtual void OnClearing(EventArgs e) { if (Clearing != null) Clearing(this, e); } protected override void ClearItems() { OnClearing(EventArgs.Empty); base.ClearItems(); } }