在WPF中使用虚拟化包装面板的选项并不多。由于某种原因,MS决定不在标准库中发货。
如果有人能够如此大胆地为下面的codeplex项目的第一个工作项提供人群来源答案(和解释),我将不胜感激:
http://virtualwrappanel.codeplex.com/workitem/1
谢谢!
问题摘要:
我最近尝试过使用此项目中的虚拟化包装并遇到了一个错误。
重现的步骤:
Debug.Assert失败(Debug.Assert(child == _children [childIndex],“生成了错误的子项”);)在MeasureOverride中,继续执行会导致Cleanup方法中出现空异常[请参阅附件截图] 。
如果你能纠正这个问题,请告诉我。
谢谢,
AO
代码:
http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#
答案 0 :(得分:8)
问题说明
您要求解释出现问题的原因以及如何解决问题的说明。到目前为止,没有人解释过这个问题我会这样做的。
在带有VirtualizingWrapPanel的ListBox中,有五个独立的数据结构,它们以不同的方式跟踪项目:
从ItemsSource中删除项目时,必须通过所有数据结构传播此删除。以下是它的工作原理:
因此,InternalChildren集合与其他四个集合不同步,导致出现错误。
解决问题的方法
要解决此问题,请在VirtualizingWrapPanel的OnItemsChanged方法中的任何位置添加以下代码:
switch(args.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
break;
case NotifyCollectionChangedAction.Move:
RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
break;
}
这使InternalChildren集合与其他数据结构保持同步。
为什么不在这里调用AddInternalChild / InsertInternalChild
您可能想知道为什么在上面的代码中没有调用InsertInternalChild或AddInternalChild,特别是为什么处理Replace和Move不需要我们在OnItemsChanged期间添加新项目。
理解这一点的关键在于ItemContainerGenerator的工作方式。
当ItemContainerGenerator收到删除事件时,它立即处理所有事件:
另一方面,ItemContainerGenerator了解到添加的项目通常是延期的:
因此,来自InternalChildren集合的所有删除(包括属于Move或Replace的那些)必须在OnItemsChanged内完成,但添加可以(并且应该)推迟到下一个MeasureOverride。
答案 1 :(得分:4)
OnItemsChanged方法需要正确处理args参数。有关详细信息,请参阅此question。从该问题中复制代码,您需要像这样更新OnItemsChanged:
protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) {
base.OnItemsChanged(sender, args);
_abstractPanel = null;
ResetScrollInfo();
// ...ADD THIS...
switch (args.Action) {
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
break;
case NotifyCollectionChangedAction.Move:
RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
break;
}
}
答案 2 :(得分:0)
首先,请注意,一般情况下,如果要从集合中删除对象并且没有它的引用,那么该对象在删除时就已经死了。因此,移除后至少RemoveInternalChildRange调用是非法的,但这不是核心问题。
其次,你可能会遇到一些竞争条件,即使它不是严格的多线程。必须检查(使用断点)该事件处理程序是否过于急切地做出反应 - 您不希望事件处理程序在您仍处于删除过程中时运行,即使它是单个项目。
第三,在以下后检查为空:
UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
并且对于第一次试验,将代码更改为优雅退出,在这种情况下意味着gracefull continue - 必须在循环中使用for循环和增量才能继续执行。
同时检查InternalChildren,当你看到null时,看看该访问路径是否与_children给出相同的结果(如同大小,内部数据,在同一个地方为null)。
如果只是跳过null幸存(没有异常渲染),请在调试器之后立即停止它并检查这些数组/集合是否已解决(内部没有空值)。
此外,发布完全可编辑的示例项目,该项目在某处提供repro(作为zip文件) - 减少随机假设并允许ppl只构建/运行并查看。
说到假设 - 检查你的“可观察集合”是做什么的。 如果要从集合中删除某个项,则该集合的先前状态中的任何迭代器/枚举器都有权抛出或赋予空值,并且在尝试过于智能的UI中具有陈旧的迭代器很容易发生。