Подтвердить что ты не робот

Виртуализация WPF Listbox создает DisconnectedItems

Я пытаюсь создать элемент управления Graph с помощью WPF ListBox. Я создал свой собственный Canvas, который происходит из VirtualizingPanel, и я сам справляюсь с реализацией и виртуализацией элементов.

Теперь панель элементов списка содержит личный виртуализированный холст.

Проблема, с которой я сталкиваюсь, происходит в следующем сценарии:

  • Сначала создается элемент списка ListBox.
  • ListBox Элемент B создается справа от элемента A на холсте.
  • ListBox Элемент A сначала виртуализируется (путем панорамирования его вне поля зрения).
  • Элемент списка ListBox B виртуализируется второй (опять же путем панорамирования его вне поля зрения).
  • Принесите ListBox Item A и B в поле зрения (то есть: реализуйте их)
  • Используя Snoop, я обнаруживаю, что ListBox теперь имеет 3 элемента, один из которых является "DisconnectedItem", расположенным непосредственно под элементом списка ListBox.

Что вызывает создание этого "DisconnectedItem"? Если бы я сначала виртуализовал B, а затем A, этот элемент не будет создан. Моя теория заключается в том, что элементы виртуализации, которые предшествуют другим элементам в ListBox, заставляют отключать детей.

Проблема еще более очевидна с использованием графика с сотнями узлов, так как я заканчиваю сотнями отключенных элементов при обходе вокруг.

Вот часть кода для холста:

/// <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));
    }
4b9b3361

Ответ 1

Он используется всякий раз, когда контейнер удаляется из визуального дерева, либо потому, что соответствующий элемент был удален, либо сбор обновлен, либо контейнер был прокручен с экрана и повторно виртуализирован.

Это известная ошибка в WPF 4

Смотрите эту ссылку для известной ошибки, она также имеет обходное решение, которое вы можете применить.

ИЗМЕНИТЬ:

"Вы можете сделать свое решение немного более надежным, сохранив ссылку на объект-опознаватель {DisconnectedItem} при первом просмотре, а затем сравните с сохраненным значением после этого.

Мы должны были сделать общедоступный способ проверить {DisconnectedItem}, но он проскользнул через трещины. Мы исправим это в будущей версии, но пока вы можете рассчитывать на то, что существует уникальный объект {DisconnectedItem}.

Ответ 2

Я опоздал на 6 лет, но проблема все еще не решена в WPF. Здесь - это решение (обходной путь).

Сделайте самосвязывание с DataContext, например:

<Image DataContext="{Binding}" />

Это сработало для меня, даже для очень сложного xaml.