WPF Listbox Virtualization creates DisconnectedItems

余生颓废 提交于 2019-11-30 22:45:54

问题


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.

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.

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.

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

回答1:


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

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.

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."




回答2:


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

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

<Image DataContext="{Binding}" />

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



来源:https://stackoverflow.com/questions/14282894/wpf-listbox-virtualization-creates-disconnecteditems

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!