Как ускорить добавление элементов в ListView?

84

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

    введите описание изображения здесь

    Проверьте

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

    введите описание изображения здесь

    Проверьте

  • Шаг 3 - спросите stackoverflow:

    введите описание изображения здесь

    Проверьте

Примечание. Очевидно, что этот 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 мне не хватает?

Ян Бойд
источник
2
Боковое примечание: при использовании флажков вы должны установить отмеченное состояние перед добавлением в ListView. Инициализация проверенных состояний blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx
Тим
3
Я должен спросить: почему вы добавляете ТАКОЕ много элементов?
OO
4
Отличный вопрос, Ян. Вы видели этот блог на эту тему? virtualdub.org/blog/pivot/entry.php?id=273
Крис Шейн,
2
1349 мс, невозможно. Я пробую с 53709 предметами, это занимает несколько минут. Зачем использовать список с таким количеством элементов? , на самом деле не используется. Вы можете использовать listBox или comboBox для увеличения скорости, но это безумное число
Xilmiki
4
Почему бы не использовать виртуальный список? Хорошо, вам нужно запрограммировать, как получать данные об элементах, и вам, возможно, придется поддерживать другие вещи, такие как сортировка, фильтрация и т. Д., Но вы заполните представление списка мгновенно, независимо от того, сколько элементов.
Casperah

Ответы:

22

Я взглянул на исходный код представления списка и заметил несколько вещей, которые могут снизить производительность примерно в 4 раза, как вы видите:

в ListView.cs ListViewItemsCollection.AddRangeвызывает вызовы ListViewNativeItemCollection.AddRange, с которых я начал свой аудит

ListViewNativeItemCollection.AddRange(из строки: 18120) имеет два прохода через всю коллекцию значений, один для сбора всех отмеченных элементов, другой для их «восстановления» после вызова InsertItems(они оба защищены проверкой owner.IsHandleCreated, владельцем является ListView), затем вызывает BeginUpdate.

ListView.InsertItems(из строки: 12952), первый вызов, имеет еще один обход всего списка, затем вызывается ArrayList.AddRange (возможно, еще один проход), а затем еще один проход после этого. Ведущий к

ListView.InsertItems(из строки: 12952), второй вызов (через EndUpdate) еще один проход, через который они добавляются в a HashTable, и a Debug.Assert(!listItemsTable.ContainsKey(ItemId))будет еще больше замедлять его в режиме отладки. Если дескриптор не создан, он добавляет элементы в ArrayList, listItemsArrayно if (IsHandleCreated)затем вызывает

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

Эрикест
источник
10

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

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

Slav2
источник
4
Да, активный сортировщик при добавлении элементов *** чрезвычайно медленный. Но в этом случае сортировщика у меня нет. Но это полезный первый шаг для людей, которые могут не осознавать, что .NET listview вызывает сортировку каждый раз при добавлении элемента, а не в конце.
Ian Boyd
0

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

this.listViewAbnormalList.ListViewItemSorter = null;

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

 lv.ListViewItemSorter = new ListViewColumnSorter()

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

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
Батур
источник
Slav2 предложил это . Что я также предложил в своем первоначальном вопросе.
Ян Бойд
Да, это то же самое, что и ответ выше. :)
Батур
-1

Поле ListView Добавить

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

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

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

Деметра Фиппс
источник
Это простой способ добавить один элемент. Но это не самый быстрый способ добавить 75 000 элементов.
Ян Бойд,
Я согласен. Я опубликую реализацию для добавления нескольких результатов с созданием экземпляра этого класса ListView.
Demetre Phipps
-2

Создайте все свои 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 );
Ахазза
источник