Как ускорить добавление элементов в 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 77

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 сделать это так медленно. Сделайте сортировщик как null

this.listViewAbnormalList.ListViewItemSorter = null;

затем когда нажмите кнопку сортировщик, на ListView_ColumnClick способ , чтобы сделать это

 lv.ListViewItemSorter = new ListViewColumnSorter()

наконец, после того, как он был отсортирован, сделайте sorter нуль снова

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;

ListView Box Добавить

Это простой код, который я смог построить, чтобы добавить элементы в список, состоящий из столбцов. Первый столбец-это товар, а второй столбец-цена. Код ниже печатает пункт Корица в первом столбце и 0.50 во втором столбце.

// How to add ItemName and Item Price
listItems.Items.Add("Cinnamon").SubItems.Add("0.50");

не требуется создание экземпляра.

создать все ваши ListViewItemsпервый, затем добавьте их в ListView все сразу.

например:

    var theListView = new ListView();
    var items = new ListViewItem[ 53709 ];

    for ( int i = 0 ; i < items.Length; ++i )
    {
        items[ i ] = new ListViewItem( i.ToString() );
    }

    theListView.Items.AddRange( items );