Как ускорить добавление элементов в ListView?
я добавляю несколько тысяч (например, 53,709) элементов в WinForms ListView.
Попытка 1:13,870 ms
foreach (Object o in list)
{
ListViewItem item = new ListViewItem();
RefreshListViewItem(item, o);
listView.Items.Add(item);
}
это работает очень плохо. Очевидное первое исправление-позвонить BeginUpdate/EndUpdate
.
Попытка 2:3,106 ms
listView.BeginUpdate();
foreach (Object o in list)
{
ListViewItem item = new ListViewItem();
RefreshListViewItem(item, o);
listView.Items.Add(item);
}
listView.EndUpdate();
это лучше, но все же на порядок медленнее. Давайте отделим создание ListViewItems от добавления ListViewItems, поэтому мы найдем фактическое виновник:
Попытка 3:2,631 ms
var items = new List<ListViewItem>();
foreach (Object o in list)
{
ListViewItem item = new ListViewItem();
RefreshListViewItem(item, o);
items.Add(item);
}
stopwatch.Start();
listView.BeginUpdate();
foreach (ListViewItem item in items)
listView.Items.Add(item));
listView.EndUpdate();
stopwatch.Stop()
реальное узкое место-добавление элементов. Давайте попробуем преобразовать его в AddRange
, а не foreach
попытка 4:2,182 ms
listView.BeginUpdate();
listView.Items.AddRange(items.ToArray());
listView.EndUpdate();
немного лучше. Давайте убедимся, что узкое место не находится в ToArray()
Попытка 5:2,132 ms
ListViewItem[] arr = items.ToArray();
stopwatch.Start();
listView.BeginUpdate();
listView.Items.AddRange(arr);
listView.EndUpdate();
stopwatch.Stop();
ограничение, кажется, добавление элементов в элемент управления ListView. Может быть, другая перегрузка AddRange
, где мы добавляем ListView.ListViewItemCollection
а не массив
попытка 6:2,141 ms
listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();
Ну это не лучше.
теперь пришло время растянуть:
-
Шаг 1 - убедитесь, что ни один столбец не установлен в "авто-ширина":
Регистрация
-
шаг 2 - убедитесь, что ListView не пытается сортировать элементы каждый раз, когда я добавляю один:
Регистрация
-
Шаг 3 - спросите stackoverflow:
Регистрация
Примечание: очевидно, что этот ListView не находится в виртуальном режиме; поскольку вы не можете/не можете "добавить" элементы в виртуальный список (вы устанавливаете VirtualListSize
). К счастью, мой вопрос не о представлении списка в виртуальном режиме.
есть ли что-то, что мне не хватает, что может объяснить добавление элементов в listview так медленно?
Бонус Треп
я знаю, что класс Windows ListView может сделать лучше, потому что я могу написать код, который делает это в 394 ms
:
ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
ListView1.Items.Add();
ListView1.Items.EndUpdate;
который по сравнению с эквивалентным кодом C#1,349 ms
:
listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
listView.Items.Add(new ListViewItem());
listView.EndUpdate();
порядок магнитуда быстрее.
какое свойство оболочки WinForms ListView мне не хватает?
5 ответов:
я взглянул на исходный код для представления списка, и я заметил несколько вещей, которые могут замедлить производительность в 4 раза или так, что вы видите:
в ListView.cs,
ListViewItemsCollection.AddRange
звонкиListViewNativeItemCollection.AddRange
, где я начал свой аудит
ListViewNativeItemCollection.AddRange
(из строки: 18120) имеет два прохода через всю коллекцию значений, один для сбора всех проверенных элементов другой для "восстановления" их послеInsertItems
называется (они оба охраняют отowner.IsHandleCreated
, собственником являетсяListView
) затем называетBeginUpdate
.
ListView.InsertItems
(из строки: 12952), первый вызов, имеет еще один ход всего списка, а затем ArrayList.AddRange называется (вероятно, еще один проход там), а затем еще один проход после этого. Ведущий к
ListView.InsertItems
(от линии: 12952), второй звонок (черезEndUpdate
) еще один проход, где они добавляются кHashTable
иDebug.Assert(!listItemsTable.ContainsKey(ItemId))
будет замедлять его дальше в режиме отладки. Если дескриптор не создан, он добавляет элементы в АнArrayList
,listItemsArray
ноif (IsHandleCreated)
, потом он называет
ListView.InsertItemsNative
(из строки: 3848) окончательный проход через список, где он фактически добавлен в собственный listview. аDebug.Assert(this.Items.Contains(li)
дополнительно замедлит производительность в режиме отладки.таким образом, есть много дополнительных проходов через весь список элементов в элементе управления .net, прежде чем он когда-либо сможет фактически вставить элементы в собственный listview. Некоторые проходы охраняли проверок ручка создано, поэтому, если вы можете добавлять элементы до создания дескриптора, это может сэкономить вам некоторое время. Элемент
OnHandleCreated
метод принимаетlistItemsArray
и звонкиInsertItemsNative
напрямую без лишней шумихи.вы можете прочитать
ListView
код источник сами и посмотрите, может быть, я что-то пропустил.в номере журнала MSDN за март 2006 года там была статья под названием
Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps
.эта статья содержит советы для повышения производительности ListViews, среди прочего. Это, кажется, указывает на то, что его быстрее добавлять элементы до создания дескриптора, но что вы будете платить цену, когда элемент управления отображается. Возможно, применение оптимизаций рендеринга, упомянутых в комментариях, и добавление элементов до создания дескриптора получат лучшее из обоих миров.
Edit: проверил эту гипотезу различными способами, и при добавлении элементов Перед созданием дескриптора является suuuper быстро, экспоненциально медленнее, когда он идет, чтобы создать ручку. Я играл с попыткой обмануть его, чтобы создать ручку, а затем каким-то образом заставить его вызвать InsertItemsNative, не проходя через все дополнительные проходы, но, увы, мне помешали. Единственное, что я мог бы подумать, это создать свой Win32 ListView в проекте c++, наполнить его элементами и использовать hooking для захвата сообщения CreateWindow, отправленного ListView при создании его дескриптора, и передать ссылку на win32 ListView вместо нового окна.. но кто знает, на что там повлияет сторона... гуру Win32 должен был бы говорить об этой сумасшедшей идее :)
я использовал этот код:
ResultsListView.BeginUpdate(); ResultsListView.ListViewItemSorter = null; ResultsListView.Items.Clear(); //here we add items to listview //adding item sorter back ResultsListView.ListViewItemSorter = lvwColumnSorter; ResultsListView.Sort(); ResultsListView.EndUpdate();
Я тоже
GenerateMember
false для каждого столбца.ссылка на пользовательский сортировщик списка:http://www.codeproject.com/Articles/5332/ListView-Column-Sorter
у меня та же проблема. Затем я обнаружил, что это
sorter
сделать это так медленно. Сделайте сортировщик как nullthis.listViewAbnormalList.ListViewItemSorter = null;
затем когда нажмите кнопку сортировщик, на
ListView_ColumnClick
способ , чтобы сделать этоlv.ListViewItemSorter = new ListViewColumnSorter()
наконец, после того, как он был отсортирован, сделайте
sorter
нуль снова((System.Windows.Forms.ListView)sender).Sort(); lv.ListViewItemSorter = null;
Это простой код, который я смог построить, чтобы добавить элементы в список, состоящий из столбцов. Первый столбец-это товар, а второй столбец-цена. Код ниже печатает пункт Корица в первом столбце и 0.50 во втором столбце.
// How to add ItemName and Item Price listItems.Items.Add("Cinnamon").SubItems.Add("0.50");
не требуется создание экземпляра.