Як пришвидшити додавання елементів до ListView?


83

я додаю кілька тисяч (наприклад, 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
Я повинен запитати: чому ви додаєте ТО багато предметів?
ОО

4
Чудове питання Ян. Ви бачили цей блог на цю тему? virtualdub.org/blog/pivot/entry.php?id=273
Кріс Шейн,

2
1349 мс, неможливо. Я намагаюся з 53709 предметами це займає кілька хвилин. Навіщо використовувати подання списку з такою кількістю елементів? , насправді не придатний для використання. Ви можете використовувати listBox або comboBox для збільшення швидкості, але це
шалене

4
Чому б не використовувати віртуальний перегляд списку? Гаразд, вам потрібно запрограмувати спосіб отримання даних про елементи, і, можливо, вам доведеться підтримувати інші речі, такі як сортування, фільтрація тощо, але ви одразу ж заповните подання списку, незалежно від кількості елементів.
Каспера

Відповіді:


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


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;

Слав2 припустив це . Що я також запропонував у своєму початковому питанні.
Ян Бойд,

Так, це те саме, що і відповідь вище. :)
Батур

-1

ListView Box Add

Це простий код, який я зміг створити для додавання елементів до списку, що складається зі стовпців. Перший стовпець - товар, а другий - ціна. Код нижче друкує позицію Кориця в першій колонці та 0,50 у другій колонці.

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

Екземпляр не потрібен.


Це простий спосіб додати один предмет. Але це не швидкий спосіб додати 75 000 предметів.
Ian Boyd

Я згоден. Я опублікую реалізацію для додавання декількох результатів із створенням цього класу ListView.
Demetre Phipps

-2

Створити всі ваші 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 );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.