cpl*_*tts 89 wpf observablecollection
我这里有些东西让我措手不及.
我有一个带有项目的Ob的ObservableCollection.我还有一个附加到CollectionChanged事件的事件处理程序.
当您清除集合,将导致以e.Action的CollectionChanged事件设置为NotifyCollectionChangedAction.Reset.好的,这很正常.但奇怪的是,e.OldItems或e.NewItems都没有任何内容.我希望e.OldItems可以填充从集合中删除的所有项目.
有没有人见过这个?如果是这样,他们是如何绕过它的?
一些背景:我使用CollectionChanged事件来附加和分离另一个事件,因此如果我没有在e.OldItems中获取任何项目......我将无法从该事件中分离出来.
澄清: 我知道文档并没有完全声明它必须以这种方式行事.但对于其他所有行动,它都会告诉我它做了什么.所以,我的假设是它会告诉我......在清除/重置的情况下.
如果您希望自己重现,下面是示例代码.首先关闭xaml:
<Window
x:Class="ObservableCollection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<StackPanel>
<Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
<Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
<Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
<Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
<Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
</StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)
接下来,代码背后:
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
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
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<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
}
}
Run Code Online (Sandbox Code Playgroud)
Ori*_*rds 46
它不声称包含旧项目,因为重置并不意味着列表已被清除
这意味着发生了一些戏剧性的事情,并且计算添加/删除的成本很可能超过仅从头开始重新扫描列表的成本......所以这就是你应该做的.
MSDN建议将整个集合重新排序为重置候选者的示例.
重申一下.重置并不意味着明确,这意味着您对该列表的假设现在无效.把它看作是一个全新的清单.Clear恰好是其中的一个例子,但其他人可能也是如此.
一些例子:
我有一个这样的列表,其中包含很多项目,并且它已被数据绑定到WPF ListView以显示在屏幕上.
如果你清除列表并提升.Reset事件,那么性能几乎是即时的,但是如果你提出很多单独的.Remove事件,性能就会很糟糕,因为WPF会逐个删除这些项目.我还在.Reset我自己的代码中使用来表明列表已经重新排序,而不是发出数千个单独的Move操作.与Clear一样,在筹集许多个人活动时会有很大的性能影响.
dec*_*jau 21
我们在这里遇到了同样的问题.CollectionChanged中的Reset操作不包含OldItems.我们有一个解决方法:我们使用以下扩展方法:
public static void RemoveAll(this IList list)
{
while (list.Count > 0)
{
list.RemoveAt(list.Count - 1);
}
}
Run Code Online (Sandbox Code Playgroud)
我们最终不支持Clear()函数,并在CollectionChanged事件中为Reset操作抛出NotSupportedException.RemoveAll将使用正确的OldItems在CollectionChanged事件中触发Remove操作.
gra*_*tnz 13
另一个选项是使用单个Remove事件替换Reset事件,该事件在其OldItems属性中包含所有已清除的项,如下所示:
public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
List<T> removed = new List<T>(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
...
}
Run Code Online (Sandbox Code Playgroud)
好处:
无需订阅其他活动(按照接受的答案要求)
不为每个删除的对象生成事件(其他一些建议的解决方案会导致多个已删除的事件).
订阅者只需要在任何事件上检查NewItems和OldItems,以根据需要添加/删除事件处理程序.
缺点:
没有重置事件
小(?)开销创建列表副本.
???
编辑2012-02-23
不幸的是,当绑定到基于WPF列表的控件时,使用多个元素清除ObservableCollectionNoReset集合将导致异常"不支持范围操作".要与具有此限制的控件一起使用,我将ObservableCollectionNoReset类更改为:
public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
// Some CollectionChanged listeners don't support range actions.
public Boolean RangeActionsSupported { get; set; }
protected override void ClearItems()
{
if (RangeActionsSupported)
{
List<T> removed = new List<T>(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.
}
Run Code Online (Sandbox Code Playgroud)
当RangeActionsSupported为false(默认值)时,这不是那么有效,因为集合中的每个对象都会生成一个Remove通知
我找到了一个解决方案,允许用户同时利用添加或删除多个项目的效率,同时只触发一个事件 - 并满足UIElements的需求以获取Action.Reset事件args而所有其他用户将就像添加和删除的元素列表一样.
此解决方案涉及覆盖CollectionChanged事件.当我们开始触发此事件时,我们实际上可以查看每个已注册处理程序的目标并确定其类型.由于只有ICollectionView类NotifyCollectionChangedAction.Reset在多个项目发生更改时才需要args,因此我们可以将它们单独输出,并为其他人提供包含已删除或添加的完整项目列表的正确事件参数.以下是实施.
public class BaseObservableCollection<T> : ObservableCollection<T>
{
//Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) 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<T> 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<T> removed = new List<T>(this);
_SuppressCollectionChanged = true;
base.ClearItems();
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
public void Add(IEnumerable<T> 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<T>(toAdd)));
}
public void Remove(IEnumerable<T> 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<T>(toRemove)));
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
好的,我知道这是一个非常古老的问题,但我已经想出了一个很好的解决方案,并且认为我会分享.这个解决方案从这里的很多很好的答案中获得灵感,但具有以下优点:
这是代码:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
{
unhookAction.Invoke(collection);
collection.Clear();
}
Run Code Online (Sandbox Code Playgroud)
这个扩展方法只需要Action在清除集合之前调用它.
好吧,即使我仍然希望ObservableCollection按照我的意愿行事......下面的代码就是我最终要做的.基本上,我创建了一个名为TrulyObservableCollection的新T集合,并覆盖了ClearItems方法,然后我用它来引发Clearing事件.
在使用此TrulyObservableCollection的代码中,我使用此清除事件来遍历当时仍在集合中的项目,以便对我希望从中分离的事件进行分离.
希望这种方法能够帮助其他人.
public class TrulyObservableCollection<T> : ObservableCollection<T>
{
public event EventHandler<EventArgs> Clearing;
protected virtual void OnClearing(EventArgs e)
{
if (Clearing != null)
Clearing(this, e);
}
protected override void ClearItems()
{
OnClearing(EventArgs.Empty);
base.ClearItems();
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
41220 次 |
| 最近记录: |