WPF 列表框虚拟化创建 DisconnectedItems

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14282894/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 06:59:14  来源:igfitidea点击:

WPF Listbox Virtualization creates DisconnectedItems

c#.netwpfvirtualization

提问by Hussein Khalil

I'm attempting to create a Graph control using a WPF ListBox. I created my own Canvas which derives from a VirtualizingPanel and I handle the realization and virtualization of items myself.

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

The listbox' item panel is then set to be my custom virtualized canvas.

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

The problem I am encountering occurs in the following scenario:

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

  • ListBox Item A is created first.
  • ListBox Item B is created to the right of Item A on the canvas.
  • ListBox Item A is virtualized first (by panning it out of view).
  • ListBox Item B is virtualized second (again by panning it out of view).
  • Bring ListBox Item A and B in view (i.e: realize them)
  • Using Snoop, I detect that the ListBox has now 3 items, one of them being a "DisconnectedItem" located directly underneath ListBox Item B.
  • 首先创建列表框项 A。
  • ListBox Item B 创建在画布上的 Item A 的右侧。
  • ListBox Item A 首先被虚拟化(通过将其平移到视野之外)。
  • ListBox Item B 是第二个虚拟化的(再次通过将它移出视野)。
  • 将 ListBox Item A 和 B 放在视图中(即:实现它们)
  • 使用 Snoop,我检测到 ListBox 现在有 3 个项目,其中一个是位于 ListBox Item B 正下方的“DisconnectedItem”。

What causes the creation of this "DisconnectedItem" ? If I were to virtualize B first, followed by A, this item would not be created. My theory is that virtualizing items that precedes other items in a ListBox causes children to be disconnected.

是什么导致了这个“DisconnectedItem”的产生?如果我先虚拟化 B,然后虚拟化 A,则不会创建此项目。我的理论是虚拟化列表框中其他项目之前的项目会导致子项断开连接。

The problem is even more apparent using a graph with hundreds of nodes, as I end up with hundreds of disconnected items as I pan around.

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

Here is a portion of the code for the 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));
    }

回答by Paul Zahra

It's used whenever a container is removed from the visual tree, either because the corresponding item was deleted, or the collection was refreshed, or the container was scrolled off the screen and re-virtualized.

每当从可视化树中删除容器时都会使用它,因为相应的项目被删除,或者集合被刷新,或者容器从屏幕上滚动并重新虚拟化。

This is a known bug in WPF 4

这是 WPF 4 中的一个已知错误

See this link for known bug, it also has a workaround you may be able to apply.

请参阅此链接了解已知错误,它还有一个您可以应用的解决方法。

EDIT:

编辑:

"You can make your solution a little more robust by saving a reference to the sentinel object {DisconnectedItem} the first time you see it, then comparing against the saved value after that.

“您可以在第一次看到哨兵对象 {DisconnectedItem} 时保存对它的引用,然后与保存的值进行比较,从而使您的解决方案更加健壮。

We should have made a public way to test for {DisconnectedItem}, but it slipped through the cracks. We'll fix that in a future release, but for now you can count on the fact that there's a unique {DisconnectedItem} object."

我们本应该公开测试 {DisconnectedItem},但它从裂缝中溜走了。我们将在未来的版本中修复这个问题,但现在你可以依靠一个独特的 {DisconnectedItem} 对象这一事实。”

回答by ádám Bozzay

I'm 6 years late, but the problem is still not fixed in WPF. Hereis the solution (workaround).

我迟到了 6 年,但问题在 WPF 中仍未解决。是解决方案(解决方法)。

Make a self-binding to the DataContext, eg.:

对 DataContext 进行自绑定,例如:

<Image DataContext="{Binding}" />

This worked for me, even for a very complex xaml.

这对我有用,即使对于非常复杂的 xaml 也是如此。