具体来说,这是这个问题 DataGrid 过滤器性能的后续问题,但是 StackOverflow 上还有许多与 WPF DataGrid 性能相关的类似问题。
经过大量分析和浏览 .NET 源代码后,我意识到许多性能问题,例如过滤和排序,归结为一个问题:CollectionView.Reset 事件不会回收容器(如滚动)是)。
我的意思是,不是为现有行分配新的数据上下文,而是从可视化树中删除所有行,生成并添加新行,并执行布局周期(测量和排列)。
所以主要问题是: 有人成功解决这个问题吗?例如。通过手动操作 ItemContainerGenerator,或通过创建自己的 DataGridRowsPresenter 版本?
这就是迄今为止我的方法的要点。
public class CollectionViewEx
{
public event EventHandler Refresh;
public override void Refresh()
{
Refresh?.Invoke(this, EventArgs.Empty);
}
}
public class DataGridEx : DataGrid
{
protected override OnItemsSourceChanged(IEnumerable oldSource, IEnumerable newSource)
{
if (newSource is CollectionViewEx cvx)
{
cvx.Refresh += (o,a) => OnViewRefreshing;
}
}
private void OnViewRefreshing()
{
RowsPresenter.Refresh();
}
}
public class DataGridRowsPresenterEx : DataGridRowsPresenter
{
public void Refresh()
{
var generator = (IRecyclingItemContainerGenerator)ItemContainerGenerator;
generator.Recycle(new GeneratorPosition(0, 0), ???);
RemoveInternalChildRange(0, VisualChildrenCount);
using (generator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
{
UIElement child;
bool isNewlyRealised = false;
while ((child = generator.GenerateNext(out isNewlyRealised) as UIElement) != null)
{
AddInternalChild(child);
generator.PrepareItemContainer(child);
}
}
}
}
但结果非常令人困惑 - 显然是因为我不太明白如何与 ICG 合作。
我浏览了 .net 源代码以查看它们的实现(添加/删除/替换项目时),并且还找到了一些关于如何创建新的虚拟化面板(例如 virtualizingwrappanel)的在线资源,但没有一个真正解决这个特定问题问题,我们希望将所有现有容器重新用于一组新的项目。
所以第二个问题是:任何人都可以解释这种方法是否可行吗?我该怎么做?
我从不直接从 CollectionView 使用重置,因为 CollectionView 的 Source 上的方法可以做到这一点。这个 IList 是根据我的需要修改的。我按照 Paul McClean 解释的那样做了这里。
在这个类中你可以通知OnCollectionChanged来通知CollectionView。 sondergard 解释了 NotifyCollectionChangedAction.Reset 的作用。但 NotifyCollectionChangedAction.Replace 继续运行项目的回收。
也许我的研究有帮助。
我最近在一个相当复杂的 DataGrid 中遇到了同样的问题,并设法找到了一个有效的解决方案here。
这有点 hacky,因为它使用反射来访问回收队列,但它工作得很好。 我所做的是用另一个类包装集合,该类拦截
Reset
并保存所有当前项目,引发 CollectionChanged
事件,然后将所有这些项目放回到(内部)回收队列中。
class MyGridCollection : IList, INotifyCollectionChanged
{
IList _collection;
swc.DataGrid _grid;
swcp.DataGridRowsPresenter _presenter;
public MyGridCollection(IList collection, swc.DataGrid grid)
{
_collection = collection;
if (_collection is INotifyCollectionChanged collectionChanged)
collectionChanged.CollectionChanged += OnCollectionChanged;
_grid = grid;
}
public void Unregister()
{
if (_collection is INotifyCollectionChanged collectionChanged)
collectionChanged.CollectionChanged -= OnCollectionChanged;
}
public object this[int index]
{
get => _collection[index];
set => _collection[index] = value;
}
public bool IsReadOnly => _collection.IsReadOnly;
public bool IsFixedSize => _collection.IsFixedSize;
public int Count => _collection.Count;
public object SyncRoot => _collection.SyncRoot;
public bool IsSynchronized => _collection.IsSynchronized;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public int Add(object value) => _collection.Add(value);
public void Clear() => _collection.Clear();
public bool Contains(object value) => _collection.Contains(value);
public void CopyTo(Array array, int index) => _collection.CopyTo(array, index);
public IEnumerator GetEnumerator() => _collection.GetEnumerator();
public int IndexOf(object value) => _collection.IndexOf(value);
public void Insert(int index, object value) => _collection.Insert(index, value);
public void Remove(object value) => _collection.Remove(value);
public void RemoveAt(int index) => _collection.RemoveAt(index);
static readonly FieldInfo s_queueField = typeof(swc.ItemContainerGenerator).GetField("_recyclableContainers", BindingFlags.Instance | BindingFlags.NonPublic);
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.Action == NotifyCollectionChangedAction.Reset && s_queueField != null)
{
_presenter ??= _grid.FindChild<swcp.DataGridRowsPresenter>();
if (_presenter != null)
{
var existingRows = new List<sw.DependencyObject>();
IList list = _presenter.Children;
for (int i = 0; i < list.Count; i++)
{
if (list[i] is sw.DependencyObject child)
existingRows.Add(child);
}
CollectionChanged?.Invoke(this, args);
if (s_queueField.GetValue(_grid.ItemContainerGenerator) is Queue<sw.DependencyObject> queue)
{
for (int i = 0; i < existingRows.Count; i++)
{
queue.Enqueue(existingRows[i]);
}
return;
}
}
}
CollectionChanged?.Invoke(this, args);
}
}
可以像这样使用:
myDataGrid.ItemsSource = new MyGridCollection(actualCollection, myDataGrid);
这可以使性能至少达到可接受的水平。 理想情况下,有一种方法可以在不反思的情况下做到这一点,但我还没有找到一种方法来做到这一点。