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