2010-07-28 105 views
8

虚拟化包装面板在WPF中使用的选项并不多。由于某种原因,MS决定不在标准库中发货。虚拟化WPF包装面板问题

如果任何人都可以大胆地提供以下CodePlex项目人群源的答案(和交代)的第一项工作,我将不胜感激:

http://virtualwrappanel.codeplex.com/workitem/1

谢谢!


总结问题:

我已经使用从该项目中的虚拟化wrappanel最近想和所遇到的错误。

重现步骤:

  1. 创建列表框。
  2. 将虚拟化wrappanel设置为listboxpanel模板中的itemhost。
  3. 将列表框的itemsource绑定到可观察集合。
  4. 从后台可观察集合中移除一个项目。

的Debug.Assert的失败(Debug.Assert的(子== _children [childIndex], “错误的子生成”);)中的MeasureOverride,并在清理方法一个空的异常持续执行结果[见附加截图]。

请让我知道,如果你能纠正这一点。

感谢,

AO


代码:

http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#

alt text http://virtualwrappanel.codeplex.com/Project/Download/AttachmentDownload.ashx?ProjectName=virtualwrappanel&WorkItemId=1&FileAttachmentId=138959

回答

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; 
    } 
} 
0

首先,要注意,在一般情况下,如果你是从集合移除对象你没有它的参考,那对象在移除时死亡。所以RemoveInternalChildRange调用至少在移除后是非法的,但这不是核心问题。

其次,你可能有一个小的竞争条件,即使它不是严格的多线程。必须检查(带有断点)该事件处理程序是否过于热切地反应 - 即使它是单个项目,也不希望事件处理程序正在运行,但仍处于删除过程中。

三,检查空后:

UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement; 

和一审修改代码,有一个体面退出,在这种情况下意味着gracefull继续 - 已在循环中使用for循环和增量能够继续下去。

另外检查InternalChildren,当你看到null,看看该访问路径是否给出与你的_children相同的结果(如大小,内部数据,null在同一地点)。

如果只是跳过一个null生存(渲染没有例外),在调试器中立即停止它,并检查这些数组/集合是否已解决(内部没有空值)。

另外,发布完全可编译的示例项目,它可以在某处提供repro(作为zip文件) - 减少随机assumprions并允许ppl构建/运行并查看。

说到假设 - 检查你的“可观察集合”在做什么。 如果您要从集合中删除项目,那么来自该集合之前状态的任何和每个迭代器/枚举器都有权抛出或给出空值,并且在尝试过于智能的UI中,可能会发生过时的迭代器容易。

8

问题

你问的什么错误,以及说明如何解决的解释说明它。到目前为止,没有人解释这个问题。我会照办的。

在具有VirtualizingWrapPanel列表框有跟踪项目,每个项目以不同的方式五个独立的数据结构:

  1. 的ItemsSource:原来的集合(在这种情况下的ObservableCollection)
  2. 的CollectionView:保持一个单独的列表排序/过滤/分组项目(仅当使用这些功能中的任何一个时)
  3. ItemContainerGenerator:跟踪项目和容器之间的映射
  4. InternalChildren:跟踪当前可见的容器勒
  5. WrapPanelAbstraction:跟踪哪些容器出现在哪一行

当物品从的ItemsSource去除,该去除必须通过所有的数据结构被传播。下面是它如何工作的:

  1. 您呼吁的ItemsSource
  2. 的ItemsSource删除()移除该项目,并触发其CollectionChanged这是由的CollectionView
  3. 的CollectionView处理删除的项目(如排序/过滤/分组是在使用中),并触发其CollectionChanged其由ItemContainerGenerator
  4. ItemContainerGenerator更新其映射处理,触发其ItemsChanged其通过VirtualizingPanel处理
  5. VirtualizingPanel调用其虚拟OnItemsChanged方法whic h由VirtualizingWrapPanel实现
  6. VirtualizingWrapPanel放弃其WrapPanelAbstraction所以将建成,但它永远不会更新InternalChildren

正因为如此,在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没有电话,特别是为什么处理更换和移动并不需要我们添加一个OnItemsChanged期间的新项目。

理解这一点的关键在于ItemContainerGenerator的工作方式。

当ItemContainerGenerator接收它处理的一切立即删除事件:

  1. ItemContainerGenerator立即从自己的数据结构
  2. ItemContainerGenerator将触发ItemChanged事件中删除的项目。预计该小组将立即移除该容器。
  3. ItemContainerGenerator“都会使得准备”通过去除其的DataContext

在另一方面,容器,ItemContainerGenerator得知项目被添加一切通常推迟:

  1. ItemContainerGenerator立即增加了一个“槽”为该项目在其数据结构中但不创建容器
  2. ItemContainerGenerator触发ItemChanged事件。面板调用InvalidateMeasure()[这是由基类完成的 - 你不必这样做]
  3. 稍后当调用MeasureOverride时,Generator.StartAt/MoveNext用于生成项容器。任何新生成的容器当时都会被添加到InternalChildren中。

因此,从InternalChildren集合(包括那些是移动的部位或更换)全部清除里面必须要OnItemsChanged做,但增加的部分可以(也应该)被推迟,直到下一次的MeasureOverride。

+0

看起来像Tom Goff在输入我的答案时提供了必要的代码。他的回答也是正确的,如果没有详细的解释,基本上和我一样。 – 2010-08-18 21:15:07

+0

嗨雷 - 尼斯总结,你有我的投票。答案的一个问题是,问题不在于“InternalChildren集合与其他四个集合不同步”,但我相信它没有帮助。 潜在的问题是,实现的孩子没有“清理”。如果您删除了索引为10的项目,那么索引为11的项目将被移至索引10.当您在索引10(先前为11)处实现该项目时,您将最终断言“错误的孩子被生成“,因为另一个孩子从未被实现。 – CodeNaked 2010-08-19 13:23:06