虚拟化WPF Wrap Panel问题

Alb*_*eld 8 wpf codeplex .net-3.5

用于WPF的虚拟化包装面板的选项不是很多.由于某种原因,MS决定不在标准库中发货.

如果有人能够如此大胆地为下面的codeplex项目的第一个工作项提供人群来源答案(和解释),我将不胜感激:

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

谢谢!


问题摘要:

我最近尝试过使用此项目中的虚拟化包装并遇到了一个错误.

重现步骤:

  1. 创建列表框.
  2. 将virtualizing wrappanel设置为listboxpanel模板中的itemhost.
  3. 将列表框的itemsource绑定到可观察的集合.
  4. 从后备可观察集合中删除项目.

Debug.Assert失败(Debug.Assert(child == _children [childIndex],"生成了错误的子项");)在MeasureOverride中,继续执行会导致Cleanup方法中出现空异常[请参阅附件截图].

如果你能纠正这个问题,请告诉我.

谢谢,

AO


码:

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

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

Ray*_*rns 8

解释问题

您要求解释出现问题的原因以及如何解决问题的说明.到目前为止,没有人解释过这个问题 我会这样做的.

在带有VirtualizingWrapPanel的ListBox中,有五个独立的数据结构,它们以不同的方式跟踪项目:

  1. ItemsSource:原始集合(在本例中为ObservableCollection)
  2. CollectionView:保留已排序/过滤/分组项目的单独列表(仅当使用了这些功能中的任何一项时)
  3. ItemContainerGenerator:跟踪项和容器之间的映射
  4. InternalChildren:跟踪当前可见的容器
  5. WrapPanelAbstraction:跟踪哪些容器出现在哪一行上

从ItemsSource中删除项目时,必须通过所有数据结构传播此删除.下面是它的工作原理:

  1. 您在ItemsSource上调用Remove()
  2. ItemsSource删除该项并触发由CollectionView处理的CollectionChanged
  3. CollectionView删除该项(如果正在使用排序/过滤/分组)并触发由ItemContainerGenerator处理的CollectionChanged
  4. ItemContainerGenerator更新其映射,触发由VirtualizingPanel处理的ItemsChanged
  5. VirtualizingPanel调用其虚拟OnItemsChanged方法,该方法由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; 
} 
Run Code Online (Sandbox Code Playgroud)

这使InternalChildren集合与其他数据结构保持同步.

为什么不在这里调用AddInternalChild/InsertInternalChild

您可能想知道为什么在上面的代码中没有调用InsertInternalChild或AddInternalChild,特别是为什么处理Replace和Move不需要我们在OnItemsChanged期间添加新项目.

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

当ItemContainerGenerator收到remove事件时,它立即处理所有事件:

  1. ItemContainerGenerator立即从其自己的数据结构中删除该项
  2. ItemContainerGenerator触发ItemChanged事件.预计该小组将立即拆除该容器.
  3. ItemContainerGenerator通过删除其DataContext"取消准备"容器

另一方面,ItemContainerGenerator了解添加的项目通常是延迟的:

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

因此,来自InternalChildren集合的所有删除(包括属于Move或Replace的那些)必须在OnItemsChanged内完成,但添加可以(并且应该)延迟到下一个MeasureOverride.


Cod*_*ked 4

OnItemsChanged 方法需要正确处理 args 参数。请参阅此问题以获取更多信息。复制该问题的代码,您需要像这样更新 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;
    }
}
Run Code Online (Sandbox Code Playgroud)