WPF ListBox 虚拟化创建了 DisconnectedItems。

18

我正在尝试使用WPF ListBox创建一个图形控件。 我创建了自己的Canvas,它派生自VirtualizingPanel,并且我自己处理项目的实现和虚拟化。

然后将列表框的项面板设置为我的自定义虚拟化画布。

我遇到的问题发生在以下场景中:

  • 首先创建ListBox Item A。
  • 在画布上,在Item A的右侧创建ListBox Item B。
  • 首先将ListBox Item A虚拟化(通过将其滑动出视图)。
  • 第二个被虚拟化的是ListBox Item B(同样是通过将其滑动出视图)。
  • 将ListBox Item A和B带回视图中(即实现它们)
  • 使用Snoop,我检测到ListBox现在有3个项,其中一个是位于ListBox Item B直接下方的“DisconnectedItem”。

是什么导致创建这个“DisconnectedItem”?如果我首先虚拟化B,然后是A,就不会创建此项。我的理论是,在ListBox中虚拟化先于其他项的项会导致子项断开连接。

使用具有数百个节点的图形时,问题更加明显,因为当我四处移动时,我最终会得到数百个已断开连接的项。

以下是Canvas代码的一部分:

/// <summary>
/// Arranges and virtualizes child element positionned explicitly.
/// </summary>
public class VirtualizingCanvas : VirtualizingPanel
{
   (...)

    protected override Size MeasureOverride(Size constraint)
    {
        ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);

        // For some reason you have to "touch" the children collection in 
        // order for the ItemContainerGenerator to initialize properly.
        var necessaryChidrenTouch = Children;

        IItemContainerGenerator generator = ItemContainerGenerator;

        IDisposable generationAction = null;

        int index = 0;
        Rect visibilityRect = new Rect(
            -HorizontalOffset / ZoomFactor,
            -VerticalOffset / ZoomFactor,
            ActualWidth / ZoomFactor,
            ActualHeight / ZoomFactor);

        // Loop thru the list of items and generate their container
        // if they are included in the current visible view.
        foreach (object item in itemsOwner.Items)
        {
            var virtualizedItem = item as IVirtualizingCanvasItem;

            if (virtualizedItem == null || 
                visibilityRect.IntersectsWith(GetBounds(virtualizedItem)))
            {
                if (generationAction == null)
                {
                    GeneratorPosition startPosition = 
                                 generator.GeneratorPositionFromIndex(index);
                    generationAction = generator.StartAt(startPosition, 
                                           GeneratorDirection.Forward, true);
                }

                GenerateItem(index);
            }
            else
            {
                GeneratorPosition itemPosition = 
                               generator.GeneratorPositionFromIndex(index);

                if (itemPosition.Index != -1 && itemPosition.Offset == 0)
                {
                    RemoveInternalChildRange(index, 1);
                    generator.Remove(itemPosition, 1);
                }

                // The generator needs to be "reseted" when we skip some items
                // in the sequence...
                if (generationAction != null)
                {
                    generationAction.Dispose();
                    generationAction = null;
                }
            }

            ++index;
        }

        if (generationAction != null)
        {
            generationAction.Dispose();
        }

        return default(Size);
    }

   (...)

    private void GenerateItem(int index)
    {
        bool newlyRealized;
        var element = 
          ItemContainerGenerator.GenerateNext(out newlyRealized) as UIElement;

        if (newlyRealized)
        {
            if (index >= InternalChildren.Count)
            {
                AddInternalChild(element);
            }
            else
            {
                InsertInternalChild(index, element);
            }

            ItemContainerGenerator.PrepareItemContainer(element);

            element.RenderTransform = _scaleTransform;
        }

        element.Measure(new Size(double.PositiveInfinity,
                                 double.PositiveInfinity));
    }

你是回收容器吗? - paparazzo
@Blam:我不认为我是,你所说的回收容器是什么意思? - Hussein Khalil
只需在 MSDN 上搜索“回收容器”http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizationmode.aspx。这只是一个尝试,但只是一个评论。 - paparazzo
@Blam:谢谢,我尝试使用VirtualizingStackPanel并打开容器回收。不幸的是,我仍然有同样的问题,即:当项目被虚拟化时,DisconnectedItems会被生成。 - Hussein Khalil
2个回答

12
我晚了6年,但WPF问题仍未修复。这里是解决方案(变通方法)。
将自我绑定到DataContext,例如:
<Image DataContext="{Binding}" />

这对我非常有用,即使是一个非常复杂的XAML。


太棒了!我解决了与触发器和切换模板相关的问题。 - david.pfx
3
这在我的情况下不起作用,但引导我找到了另一个解决方案:我将元素的 Tag 设置为绑定项,作为备用方案,这样即使数据上下文断开连接,我仍然可以访问真实项目。例如,在 ListBoxItem 的样式中,我有这个代码:<Setter Property="Tag" Value="{Binding}"/> - Paul
你一点也不晚!!几年前就开始了,很厉害。我的问题是在排序中使用的移动操作调用NotifyCollectionChanged,我认为这不是开箱即用的,但我的解决方法是手动进行删除/插入,并在重新插入时出现了{{DisconnectedItem}} - tkefauver
链接已经失效了,很遗憾。 - Nik
它可以在网络档案馆上找到。但最重要的是在我的回答中。 - Ádám Bozzay

8
每当从可视树中移除容器时,它就会被使用,无论是因为相应的项已删除,还是集合已刷新,或者容器已滚动到屏幕外并重新虚拟化。
这是WPF 4中已知的一个bug。
请参见this link for known bug,它还有一种解决方法,您可以尝试应用它。
“您可以通过在第一次看到它时保存对哨兵对象{DisconnectedItem}的引用,然后在之后与保存的值进行比较,使您的解决方案更加健壮。
我们应该提供一种公共方法来测试{DisconnectedItem},但是它被忽略了。我们将在未来的版本中修复这个问题,但现在您可以依赖于一个唯一的{DisconnectedItem}对象。”

7
现在在stackoverflow上出现了“Microsoft Connect Has Been Retired”的消息。 微软干得好。 - Evgeny Gorbovoy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接