Виртуализация WPF переносит проблему панели

Нет очень многих опций для виртуализации, переносят панель для использования в WPF. По той или иной причине MS, решенный для не поставки один в стандартной библиотеке.

Если кто-либо мог бы быть столь полужирным, что предоставил бы исходный ответ толпы (и explaination) к первому объекту работы на следующем codeplex проекте, я буду значительно ценить его:

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

Спасибо!


Сводка проблемы:

Я недавно попытался использовать виртуализацию wrappanel из этого проекта и встретился с ошибкой.

Шаги для репродуцирования:

  1. Поле списка Create.
  2. Установите виртуализацию wrappanel как itemhost в шаблоне listboxpanel.
  3. Свяжите itemsource поля списка к заметному набору.
  4. Удалите объект из отступающего заметного набора.

Отладка. Утверждайте сбои (Отладка. Утверждайте (ребенок == _children[childIndex], "Неправильный ребенок был сгенерирован") ;) в MeasureOverride, и длительные результаты выполнения в пустой исключительной ситуации в методе Очистки [видят присоединенный снимок экрана].

Сообщите мне, можете ли Вы исправить это.

Спасибо,

АО


Код:

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

сопроводительный текст http://virtualwrappanel.codeplex.com/Project/Download/AttachmentDownload.ashx?ProjectName=virtualwrappanel&WorkItemId=1&FileAttachmentId=138959

8
задан kiamlaluno 18 August 2010 в 21:11
поделиться

3 ответа

Метод 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;
    }
}
4
ответ дан 5 December 2019 в 13:59
поделиться

Во-первых, имейте в виду, что в целом, если вы удаляете объект из коллекции и у вас нет ссылки на него, это объект мертв на момент удаления. Таким образом, по крайней мере, вызов RemoveInternalChildRange является незаконным после удаления, но это не основная проблема.

Во-вторых, у вас может быть небольшое состояние гонки, даже если оно не является строго многопоточным. Необходимо проверить (с помощью точки останова), слишком ли активно реагирует этот обработчик событий - вы не хотите, чтобы обработчик событий работал, пока вы все еще находитесь в середине удаления, даже если это один элемент.

В-третьих, проверьте наличие null после:

UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;

и для первой попытки измените код, чтобы обеспечить плавный выход, что в данном случае означает плавное продолжение - необходимо использовать цикл for и приращения в цикле, чтобы иметь возможность выполнять продолжать вообще.

Также проверьте InternalChildren, когда вы видите это значение null, чтобы увидеть, дает ли этот путь доступа тот же результат, что и ваш _children (например, размер, внутренние данные, null в том же месте).

Если просто пропуск нулевого значения выживает (отображается без исключений), сразу после этого остановите его в отладчике и проверьте, были ли эти массивы / коллекции урегулированы (внутри нет нулей).

Также опубликуйте полностью компилируемый образец проекта, который дает репро (в виде zip-файла) где-нибудь - уменьшает случайные предположения и позволяет ppl просто построить / запустить и посмотреть.

Кстати о предположениях - проверьте, что делает ваша «наблюдаемая коллекция». Если вы удаляете элемент из коллекции, любой итератор / перечислитель из предыдущего состояния этой коллекции имеет право выдавать или выдавать нули и в пользовательском интерфейсе, который пытается быть слишком умным, имея устаревший итератор может легко произойти.

0
ответ дан 5 December 2019 в 13:59
поделиться

Объяснение проблемы

Вы просили объяснить, что происходит не так, а также дать инструкции, как это исправить. Пока никто не объяснил проблему. Я сделаю так.

В ListBox с VirtualizingWrapPanel есть пять отдельных структур данных, которые отслеживают элементы, каждая по-разному:

  1. ItemsSource: исходная коллекция (в данном случае ObservableCollection)
  2. CollectionView: сохраняет отдельный список отсортированных / отфильтрованных / сгруппированные элементы (только если используется какая-либо из этих функций)
  3. ItemContainerGenerator: отслеживает сопоставление между элементами и контейнерами
  4. InternalChildren: отслеживает контейнеры, которые в настоящее время видны
  5. WrapPanelAbstraction: отслеживает, какие контейнеры появляются в какой строке

Когда элемент удаляется из ItemsSource, это удаление должно распространяться на все структуры данных. Вот как это работает:

  1. Вы вызываете Remove () для ItemsSource
  2. ItemsSource удаляет элемент и запускает его CollectionChanged, который обрабатывается CollectionView
  3. CollectionView удаляет элемент (если сортировка / фильтрация / группировка находится в use) и запускает свой CollectionChanged, который обрабатывается ItemContainerGenerator
  4. ItemContainerGenerator обновляет свое сопоставление, запускает свой ItemsChanged, который обрабатывается VirtualizingPanel
  5. VirtualizingPanel вызывает свой виртуальный метод OnItemsChanged, который реализуется VirtualizingWrapPanel
  6. VirtualizingWrapPanel он будет построен, но он никогда не обновляет InternalChildren

Из-за этого коллекция InternalChildren не синхронизирована с другими четырьмя коллекциями, что приводит к возникшим ошибкам.

Решение проблемы

Чтобы устранить проблему, добавьте следующий код в любом месте метода OnItemsChanged VirtualizingWrapPanel:

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

Это поддерживает синхронизацию коллекции InternalChildren с другими структурами данных.

Почему здесь не вызывается AddInternalChild / InsertInternalChild

Вы можете задаться вопросом, почему в приведенном выше коде нет вызовов InsertInternalChild или AddInternalChild, и особенно почему обработка Replace и Move не требует от нас добавления нового элемента во время OnItemsChanged .

Ключ к пониманию этого - способ работы ItemContainerGenerator.

Когда ItemContainerGenerator получает событие удаления, он обрабатывает все немедленно:

  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.

8
ответ дан 5 December 2019 в 13:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: