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

Ограничение похоже на добавление элементов в список. Возможно, другая перегрузка AddRange, где мы добавляем ListView.ListViewItemCollection, а не массив

Попытка 6: 2,141 ms

listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();

Хорошо, что не лучше.

Теперь пришло время растягиваться:

  • Шаг 1 - убедитесь, что для столбца не установлено значение "автоширина":

    enter image description here

    Проверить

  • Шаг 2 - убедитесь, что ListView не пытается сортировать элементы каждый раз, когда я добавляю один:

    enter image description here

    Проверить

  • Шаг 3. Задайте stackoverflow:

    enter image description here

    Проверить

Примечание: Очевидно, что этот ListView не находится в виртуальном режиме; так как вы не можете/не можете добавлять элементы в представление виртуального списка (вы устанавливаете VirtualListSize). К счастью, мой вопрос заключается не в представлении списка в виртуальном режиме.

Есть ли что-нибудь, чего я не вижу, что может объяснить добавление элементов в список так медленно?


Бонус-чат

Я знаю, что класс ListView Windows может работать лучше, потому что я могу написать код, который делает это в 394 ms:

ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
   ListView1.Items.Add();
ListView1.Items.EndUpdate;

который по сравнению с эквивалентным кодом С# 1,349 ms:

listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
   listView.Items.Add(new ListViewItem());
listView.EndUpdate();

на порядок быстрее.

Какое свойство оболочки WinForms ListView отсутствует?

Ответ 1

Я взглянул на исходный код для представления списка, и я заметил несколько вещей, которые могут замедлить производительность с коэффициентом 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 (from line: 3848) final проходит через список, в котором он фактически добавлен в собственный список. a Debug.Assert(this.Items.Contains(li) дополнительно замедлит работу в режиме отладки.

Таким образом, есть много дополнительных проходов по всему списку элементов в элементе управления .net, прежде чем он когда-либо сможет вставить элементы в собственный список. Некоторые проходы охраняются чеками против создаваемой Ручки, поэтому, если вы можете добавлять элементы до создания дескриптора, это может сэкономить вам некоторое время. Метод OnHandleCreated принимает listItemsArray и вызывает InsertItemsNative напрямую без дополнительной суеты.

Вы можете прочитать код ListView в справочный источник самостоятельно и посмотреть, возможно, я что-то пропустил.

В выпуске журнала MSDN в марте 2006 года появилась статья под названием Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

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

Изменить: Протестировать эту гипотезу различными способами, и, добавляя элементы перед созданием дескриптора, выполняется быстро, он экспоненциально медленнее, когда идет, чтобы создать дескриптор. Я играл с попыткой обмануть его, чтобы создать дескриптор, а затем каким-то образом заставить его вызвать InsertItemsNative, не пройдя все лишние проходы, но, увы, меня прервали. Единственное, что я мог подумать, возможно, это создать ваш Win32 ListView в проекте С++, наполнить его элементами и использовать привязку, чтобы захватить сообщение CreateWindow, отправленное ListView при создании его дескриптора и передать ссылку на win32 ListView вместо нового окна.., но кто знает, на что влияет сторона, будет... гуру Win32 нужно будет говорить об этой сумасшедшей идее:)

Ответ 2

Я использовал этот код:

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

Ответ 3

У меня та же проблема. Затем я обнаружил, что sorter делает это так медленно. Сделайте сортировщик равным нулю

this.listViewAbnormalList.ListViewItemSorter = null;

тогда, когда сортировщик кликов, по методу ListView_ColumnClick сделайте его

 lv.ListViewItemSorter = new ListViewColumnSorter()

Наконец, после сортировки снова сделайте sorter null

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

Ответ 4

ListView Box Добавить

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

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

Никакой инстанцирования не требуется.

Ответ 5

Создайте все свои ListViewItems FIRST, а затем добавьте их в 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 );