Как я могу повысить производительность проверки всех элементов в WPF TreeView?

#wpf #treeview

#wpf #просмотр дерева

Вопрос:

У меня есть WPF TreeView, где у каждого элемента TreeViewItem есть флажок. Моя реализация основана на примере, представленном в отличной статье Джоша Смита:http://www.codeproject.com/KB/WPF/TreeViewWithCheckBoxes.aspx. Как и в статье, поведение, которое я использую, заключается в том, что когда элемент отмечен или снят, все его дочерние элементы должны быть отмечены или сняты соответственно.

Проблема, с которой я сталкиваюсь, заключается в том, что мой TreeView может содержать десятки тысяч элементов. Итак, если все элементы изначально не отмечены, а затем я проверяю корневой элемент, может потребоваться 10-20 секунд, прежде чем все потомки будут обновлены. Это слишком медленно для моих пользователей, и мне интересно, есть ли способ ускорить обновление пользовательского интерфейса. Я совершенно уверен, что виной всему события PropertyChanged, поскольку, когда я их удаляю, проверка корневого элемента происходит почти мгновенно, хотя дочерние флажки не обновляются. Есть ли лучший способ обрабатывать обновление нескольких элементов пользовательского интерфейса одновременно? Обратите внимание, что я пытался использовать переработку контейнеров, но, похоже, это не помогло.

Если вы хотите попробовать воспроизвести это самостоятельно, вы можете загрузить проект из вышеупомянутой статьи, а затем в FooViewModel.cs измените функцию CreateFoos() на использование этого кода:

 var root = new FooViewModel("Weapons");
root.IsInitiallySelected = true;

for (int i = 0; i < 400; i  )
{
    var table = new FooViewModel("Table "   i);
    for (int j = 65; j < 91; j  )
    {
        var charItem = new FooViewModel(Convert.ToChar(j).ToString());
        for (int k = 0; k < 3; k  )
        {
            var deepItem = new FooViewModel("Item "   k);
            charItem.Children.Add(deepItem);
        }

        table.Children.Add(charItem);
    }

    root.Children.Add(table);
}

root.Initialize();
return new List<FooViewModel> { root };
  

Спасибо за любые идеи,

-Крейг

Комментарии:

1. Вы используете это в Windows Vista / 7?

2. Как указывает dlev, виртуализация, вероятно, ваш лучший выбор. Но в теме Aero флажки анимируют переходы их состояний. Таким образом, это означает, что для каждого флажка вы запускаете анимацию. Единственным способом обойти это было бы использовать пользовательский стиль флажка с ControlTemplate, который не использует элемент BulletChrome.

3. @CodeNaked: На самом деле, мы используем пользовательскую тему, но я дважды проверю, не используются ли здесь анимации, поскольку я согласен, что это также может замедлить работу. Спасибо.

Ответ №1:

Пользовательский интерфейс, представляющий древовидное представление, содержащее 10 000 элементов, вероятно, является пользовательским интерфейсом, который нуждается в переработке. (Хотя и не обязательно — диалоговое окно поиска папки по существу делает это, если ваша файловая система достаточно сложная.)

Две вещи, которые вы можете попробовать, предполагая, что вы не используете виртуализацию:

  1. Привязать TreeViewItem.IsExpanded к логическому свойству в вашей модели представления узла. Узел должен вызывать PropertyChanged события только в том случае, если IsExpanded свойство его родительского узла равно true. Это сохранит конечные узлы, которые не видны при возникновении событий, которые визуальное дерево должно игнорировать. (Вы также можете обнаружить, что вам нужно будет повышать node PropertyChanged над его IsChecked свойством всякий раз, когда IsExpanded свойство его родительского элемента становится истинным.) При полном развертывании дерева все равно будут проблемы, но если вы не представите дерево пользователю в этом состоянии с самого начала, пользователю придется поработать над переводом элемента управления в состояние, в котором у него начнутся проблемы с производительностью.

  2. Никогда не вызывайте PropertyChanged события, когда родительский узел обновляет своих потомков. Вместо этого повысьте значение модели представления владельца PropertyChanged для свойства, к которому привязан ItemsSource из TreeView . Это заставит весь элемент управления повторно отображать, но очень вероятно, что это будет быстрее, чем обработка отдельных обновлений со всех узлов. (Конечно, вам нужно будет реализовать какой-то способ, чтобы узел уведомлял своего родителя о том, что он только что обновил своих потомков — например, инициируя событие — и какой-то способ сообщить узлу, что он не должен инициировать это событие, если он обновляет своих потомков, потому что его родитель сказал ему.)

Комментарии:

1. Ваше первое предложение отлично работает. Что касается древовидного представления, отображающего так много элементов, сценарий позволяет пользователю просматривать и выбирать элементы в базе данных SQL, используя иерархию схема-таблица-столбец. Для больших баз данных мы можем получить множество элементов, хотя большинство из них не будут расширены.

Ответ №2:

К сожалению, WPF почти всегда сталкивается с проблемами производительности при работе с таким сложным визуальным деревом, как у вас, похоже, есть. Кроме того, если ваша viewmodel реализует только INotifyPropertyChanged (а не наследует от DependencyObject), то обновление 10000 привязок также, как правило, будет медленным.

Для справки, переработка контейнера будет полезна, только если ваша ItemsPanel виртуализирована (в чем я сомневаюсь, учитывая вашу проблему с производительностью).

Говоря о виртуализации, это, вероятно, лучший способ решить эту проблему. К сожалению, это, вероятно, означает написание пользовательской VirtualizingPanel. Вы можете начать, следуя цепочке здесь.

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

Комментарии:

1. Насколько я понимаю, WPF TreeViews уже использует VirtualizingStackPanels; см. bea.stollnitz.com/blog/?p=338 . Я попытался установить VirtualizingStackPanel. IsVirtualizing=»True» и VirtualizingStackPanel.VirtualizationMode =»Переработка», но это не улучшило производительность.