Как ускорить добавление элементов в ListView? - PullRequest
78 голосов
/ 25 января 2012

Я добавляю несколько тысяч (например, 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 мне не хватает?

Ответы [ 5 ]

21 голосов
/ 26 января 2012

Я взглянул на исходный код для представления списка и заметил несколько вещей, которые могут замедлить производительность в 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 нужно будет рассказать об этой безумной идее:)

8 голосов
/ 08 июля 2012

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

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

0 голосов
/ 12 сентября 2018

ListView Box Add

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

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

Инстанцирование не требуется.

0 голосов
/ 18 мая 2017

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

this.listViewAbnormalList.ListViewItemSorter = null;

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

 lv.ListViewItemSorter = new ListViewColumnSorter()

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

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
0 голосов
/ 28 января 2012

Создайте все свои 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 );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...