为什么WPF DataGrid中的ItemsSource在我的绑定集合中IGNORES GetEnumerator()



我尝试在自己的可观察集合中实现过滤。我的方法如下:我假设使用ItemsSource的控件应该在我的集合上调用IEnumerable.GetEnumerator(),以获得它应该呈现的项。因此,我定义了自己的IEnumerable.GetEnumerator()来应用过滤。

以下是相关代码:

public Func<T, bool>? Filter { get; set; }
public void Refresh() {
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset
)
);
}
IEnumerator IEnumerable.GetEnumerator()
=> Filter is null
? (IEnumerator)BaseIEnumerableGetEnumerator.Invoke(this, null)!
: this.Where(Filter).GetEnumerator();
public new IEnumerator<T> GetEnumerator()
=> Filter is null
? base.GetEnumerator()
: this.Where(Filter).GetEnumerator();
private static readonly MethodInfo BaseIEnumerableGetEnumerator
= BaseType.GetMethod(
"System.Collections.IEnumerable.GetEnumerator",
BindingFlags.NonPublic | BindingFlags.Instance
)!;

顺便说一句,我的基类是List<T>。它还实现了IListICollectionINotifyCollectionChangedINotifyPropertyChanged

现在-我设置了过滤器。什么也没发生。所以我叫Refresh()

令我惊讶的是,什么也没发生。为什么?当Reset被发送到ItemsCollection时,控件应该重新加载,在重新加载时,它应该调用GetEnumerator()

我在GetEnumerator()方法上设置了断点,但在Refresh上没有调用它。为什么?

为了澄清,我试图复制ListCollectionView的确切功能。它包含应用过滤的Refresh()方法。

我看到的另一件奇怪的事情是,我的新GetEnumerator()是由我自己的控件调用的,但它根本不是由DataGrid调用的。

更新:

正如我最近研究的那样,内置的WPF控件可能会使用一些未记录的魔术来绑定项。它们可以触发视图模型对象上的事件——这在Reflection中是可能的(AFAIK)。

IDK,在视图中使用Reflection;底层系统类型";如果可以获取项目,请使用它的索引器。在这种情况下,它不会使用GetEnumerator

我还检查了ListCollectionView的源代码:https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/windows/Data/ListCollectionView.cs

它只是使用了一种阴影集合来实现过滤。好吧,这是肯定能达到效果的一种方法。但是,过滤任何集合的最简单(如果不是最快的话)的方法是将过滤器注入其枚举中。没有创建对象,没有分配,这应该很快。而且很简单。它在我自己的控制下工作,在ItemsSource上使用foreach。很明显,foreach调用枚举器。这是没有办法的。然而,微软的控制显然要么不使用foreach,要么。。。它们操作的内容不同于原始项目集合。

ItemsControl(包括DataGrid、Listbox等)不是直接使用集合,而是通过ICollectionView使用。当集合不提供自己的ICollectionView实现时(几乎总是这样),ItemsControl本身会为其创建最合适的包装器。通常,这是CollectionView及其派生的类型。提供自己包装器的类的一个例子是CompositeCollection。它为CompositeCollectionView提供了一个包装器。

CollectionViewSource也是为集合创建ICollectionView的一种方法。包括使用GetDefaultView()方法在内,您可以返回集合的默认视图。这是ItemsControl在将集合传递给它时所使用的。对于几乎所有集合,ListCollectionView都将是一个包装器。使用生成的包装器,可以设置用于筛选、分组、排序和呈现集合视图的属性。

如果要为集合创建自己的表示规则,则需要实现ICollectionViewFactory接口。它只有一个CreateView()方法来返回集合的View包装。您必须创建自己的ICollectionView实现(基于CollectionView或ListCollectionView类更容易做到这一点)。然后在CreateView()方法中返回其实例。

好吧,DataGrid只是不使用GetEnumerator来显示项目。它使用CCD_ 28和CCD_。

因此,为了在我的集合上实现过滤,我必须创建一个包含过滤项的ShadowList

然后,我为IList.this[index]ICollection.Count提供了覆盖,以返回来自ShadowList的项目。

然后,如果添加的项目与Filter匹配,我更新了所有添加到我的集合的内容,也更新了ShadowList

它是有效的,但是有一个问题:过滤后的数据只能通过IList索引器访问。因此,如果消费控件使用它,它将获得过滤后的数据,如果不使用,它将得到原始数据。

我觉得这里更好些。我的视图模型大多数时候都需要原始集合,如果不是这样的话,我总是可以向枚举器应用添加.Where(collection.Filter)的过滤器。

GetEnumerator在复杂视图模型中被称为A LOT,因此最好是原始的List<T>枚举器。

已完成的集合(ObservableList<T>)可在GitHub上获得:https://github.com/HTD/Woof.Windows/blob/master/Woof.Windows.Mvvm/ObservableList.cs

它只是使用了一种阴影集合来实现过滤。

对于WPF DataGrid,我相信它使用了一个支持CollectionViewSource,所以过滤有点不同(正如您所说,它有一个影子集合)。

MSDN提供了一些关于使用WPF数据网格进行过滤的信息,我认为这些信息可能对您的情况有所帮助。

最新更新