Jak przyspieszyć dodawanie elementów do ListView?

84

dodaję kilka tysięcy (np. 53 709) elementów do WinForms ListView.

Próba 1 :13,870 ms

foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}

To działa bardzo źle. Oczywistym pierwszym rozwiązaniem jest zadzwonić BeginUpdate/EndUpdate.

Próba 2 :3,106 ms

listView.BeginUpdate();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}
listView.EndUpdate();

To jest lepsze, ale wciąż o rząd wielkości zbyt wolno. Oddzielmy tworzenie ListViewItems od dodawania ListViewItems, aby znaleźć rzeczywistego winowajcę:

Próba 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()

Prawdziwym wąskim gardłem jest dodawanie elementów. Spróbujmy przekonwertować go AddRangeraczej na plikforeach

Próba 4: 2,182 ms

listView.BeginUpdate();
listView.Items.AddRange(items.ToArray());
listView.EndUpdate();

Trochę lepiej. Upewnijmy się, że wąskie gardło nie znajduje się wToArray()

Próba 5: 2,132 ms

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

listView.BeginUpdate();
listView.Items.AddRange(arr);
listView.EndUpdate();

stopwatch.Stop();

Wydaje się, że ograniczeniem jest dodawanie elementów do widoku listy. Może inne przeciążenie AddRange, w którym dodajemy ListView.ListViewItemCollectionzamiast tablicy

Próba 6: 2,141 ms

listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();

Cóż, nie jest lepiej.

Teraz czas się rozciągnąć:

  • Krok 1 - upewnij się, że żadna kolumna nie jest ustawiona na „szerokość automatyczną” :

    wprowadź opis obrazu tutaj

    Czek

  • Krok 2 - Upewnij się, że ListView nie próbuje sortować elementów za każdym razem, gdy je dodam:

    wprowadź opis obrazu tutaj

    Czek

  • Krok 3 - Ask stackoverflow:

    wprowadź opis obrazu tutaj

    Czek

Uwaga: Oczywiście ten ListView nie jest w trybie wirtualnym; ponieważ nie możesz / nie możesz „dodawać” elementów do wirtualnego widoku listy (ustawiasz VirtualListSize). Na szczęście moje pytanie nie dotyczy widoku listy w trybie wirtualnym.

Czy jest coś, czego mi brakuje, co mogłoby tłumaczyć tak powolne dodawanie elementów do widoku listy?


Bonus Chatter

Wiem, że klasa Windows ListView radzi sobie lepiej, ponieważ mogę napisać kod, który robi to w 394 ms:

ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
   ListView1.Items.Add();
ListView1.Items.EndUpdate;

co w porównaniu z równoważnym kodem C # 1,349 ms:

listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
   listView.Items.Add(new ListViewItem());
listView.EndUpdate();

jest o rząd wielkości szybszy.

Jakiej właściwości opakowania WinForms ListView brakuje?

Ian Boyd
źródło
2
Uwaga boczna: jeśli używasz pól wyboru, powinieneś ustawić stan zaznaczenia przed dodaniem do ListView. Inicjowanie zaznaczonych stanów blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx
Tim Schmelter
3
Muszę zapytać: dlaczego dodajesz TAK wiele pozycji?
OO
4
Świetne pytanie Ian. Widziałeś tego bloga na ten temat? virtualdub.org/blog/pivot/entry.php?id=273
Chris Shain
2
1349 ms, niemożliwe. Próbuję z 53709 pozycjami, zajmuje to kilka minut. Po co używać widoku listy z tak wieloma elementami? , tak naprawdę nie nadaje się do użytku. Możesz użyć listBox lub comboBox, aby zwiększyć prędkość, ale jest to szalona liczba
Xilmiki
4
Dlaczego nie skorzystać z wirtualnego widoku listy? W porządku, musisz zaprogramować sposób pobierania danych pozycji i być może będziesz musiał zarządzać innymi rzeczami, takimi jak sortowanie, filtrowanie itp., Ale widok listy wypełniasz natychmiast, niezależnie od liczby pozycji.
Casperah

Odpowiedzi:

22

Rzuciłem okiem na kod źródłowy widoku listy i zauważyłem kilka rzeczy, które mogą spowolnić wydajność czterokrotnie lub tak, jak widzisz:

w ListView.cs ListViewItemsCollection.AddRangewywołania ListViewNativeItemCollection.AddRange, od którego zacząłem audyt

ListViewNativeItemCollection.AddRange(z linii: 18120) ma dwa przejścia przez cały zbiór wartości, jeden do zebrania wszystkich zaznaczonych elementów, drugi do ich „przywrócenia” po InsertItemswywołaniu (oba są chronione czekiem owner.IsHandleCreated, a właściciel jest ListView), a następnie wywołuje BeginUpdate.

ListView.InsertItems(z linii: 12952), pierwsze wywołanie, ma kolejny trawers całej listy, a następnie wywoływany jest ArrayList.AddRange (prawdopodobnie kolejny przebieg), a następnie kolejny przebieg. Prowadzący do

ListView.InsertItems(z linii: 12952), drugie wywołanie (przez EndUpdate) inne przejście, w którym są one dodawane do a HashTable, a a Debug.Assert(!listItemsTable.ContainsKey(ItemId))spowolni je dalej w trybie debugowania. Jeśli uchwyt nie został utworzony, dodaje elementy do ArrayList, listItemsArrayale if (IsHandleCreated)następnie wywołuje

ListView.InsertItemsNative(z linii: 3848) końcowe przejście przez listę, gdzie faktycznie jest dodawane do natywnego widoku listy. a Debug.Assert(this.Items.Contains(li)dodatkowo spowolni działanie w trybie debugowania.

Tak więc jest DUŻO dodatkowych przejść przez całą listę elementów w kontrolce .net, zanim zdąży się faktycznie wstawić elementy do natywnego widoku listy. Niektóre przepustki są chronione przez kontrole przed tworzonym uchwytem, ​​więc jeśli możesz dodać elementy przed utworzeniem uchwytu, może to zaoszczędzić trochę czasu. OnHandleCreatedMetoda bierze listItemsArrayi nazywa InsertItemsNativesię bezpośrednio bez wszystkich dodatkowych kłopotów.

Możesz sam przeczytać ListViewkod w źródle referencyjnym i rzuć okiem, może coś przeoczyłem.

W marcowym numerze MSDN Magazine ukazał się artykuł pt Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

Ten artykuł zawierał między innymi wskazówki dotyczące poprawy wydajności ListViews. Wydaje się, że szybsze jest dodawanie elementów przed utworzeniem uchwytu, ale zapłacisz cenę za renderowanie kontrolki. Być może zastosowanie optymalizacji renderowania wspomnianej w komentarzach i dodanie elementów przed utworzeniem uchwytu pozwoli uzyskać to, co najlepsze z obu światów.

Edycja: Przetestowałem tę hipotezę na różne sposoby i podczas dodawania elementów przed utworzeniem uchwytu jest superszybkie, jest wykładniczo wolniejsze, gdy tworzy uchwyt. Bawiłem się próbami oszukania go, aby utworzyć uchwyt, a następnie w jakiś sposób zmusiłem go do wywołania InsertItemsNative bez przechodzenia przez wszystkie dodatkowe przebiegi, ale niestety zostałem udaremniony. Jedyną rzeczą, o której mógłbym pomyśleć, może być utworzenie twojego Win32 ListView w projekcie c ++, wypełnienie go elementami i użycie przechwytywania do przechwycenia wiadomości CreateWindow wysłanej przez ListView podczas tworzenia jego uchwytu i przekazania referencji do win32 ListView zamiast nowego okna ... ale kto wie, na co wpływa strona, byłby ... guru Win32 musiałby mówić o tym szalonym pomyśle :)

Erikest
źródło
10

Użyłem tego kodu:

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();

Ustawiłem również GenerateMemberna false dla każdej kolumny.

Link do niestandardowego sortownika widoku listy: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

Slav2
źródło
4
Tak, aktywowanie sortownika podczas dodawania elementów jest *** niesamowicie *** wolne. Ale w tym przypadku nie mam sortownika. Jest to jednak przydatny pierwszy krok dla osób, które mogą nie zdawać sobie sprawy, że widok listy .NET wywołuje sortowanie za każdym razem, gdy dodawany jest element - a nie na końcu.
Ian Boyd
0

Mam ten sam problem. Potem stwierdziłem, że sorterrobi to tak wolno. Ustaw sortownik jako zerowy

this.listViewAbnormalList.ListViewItemSorter = null;

następnie, gdy sortownik kliknięć, w ListView_ColumnClickmetodzie, zrób to

 lv.ListViewItemSorter = new ListViewColumnSorter()

W końcu, po posortowaniu, sorterponownie ustaw wartość null

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
Batur
źródło
Slav2 to zasugerował . Co również zasugerowałem w moim pierwotnym pytaniu.
Ian Boyd
Tak, to samo dotyczy odpowiedzi powyżej. :)
Batur
-1

Dodaj pole ListView

To jest prosty kod, który udało mi się stworzyć, aby dodać elementy do listy, która składa się z kolumn. Pierwsza kolumna to pozycja, a druga to cena. Poniższy kod wyświetla element Cinnamon w pierwszej kolumnie i 0,50 w drugiej kolumnie.

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

Nie jest wymagana żadna instancja.

Demetre Phipps
źródło
To łatwy sposób na dodanie jednego elementu. Ale nie jest to szybki sposób na dodanie 75 000 pozycji.
Ian Boyd,
Zgadzam się. Opublikuję implementację dodawania wielu wyników z tworzeniem wystąpienia tej klasy ListView.
Demetre Phipps
-2

Tworzenie wszystkie swoje ListViewItems FIRST , a następnie dodać je do ListView wszystkie naraz.

Na przykład:

    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 );
ahazzah
źródło