it-swarm.com.ru

Как ускорить добавление элементов в 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 - убедитесь, что для столбца не установлено значение "auto-width":

    enter image description here

    Проверьте

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

    enter image description here

    Проверьте

  • Шаг 3 - Задать стек в потоке:

    enter image description here

    Проверьте

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

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


Бонус Болтовня

я знаю, что класс 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 мне не хватает?

78
Ian Boyd

Я взглянул на исходный код представления списка и заметил несколько вещей, которые могут замедлить производительность в 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) последний проход по списку, в котором он фактически добавлен в собственное представление списка. 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 в проекте c ++, заполнить его элементами и использовать перехват для захвата сообщения CreateWindow, отправленного ListView при создании его дескриптора, и передачи ссылки на win32 ListView вместо нового окна ... но кто знает, на что может повлиять сторона ... Гуру Win32 нужно будет рассказать об этой сумасшедшей идее :)

21
Erikest

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

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

8
Slav2

ListView Box Add

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

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

Нет необходимости в инстанцировании. 

0
Demetre Phipps

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

this.listViewAbnormalList.ListViewItemSorter = null;

затем при нажатии сортировщика по методу ListView_ColumnClick сделайте 

 lv.ListViewItemSorter = new ListViewColumnSorter()

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

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