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 AddRange
raczej 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.ListViewItemCollection
zamiast 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ą” :
Czek
Krok 2 - Upewnij się, że ListView nie próbuje sortować elementów za każdym razem, gdy je dodam:
Czek
Krok 3 - Ask stackoverflow:
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?
źródło
Odpowiedzi:
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.AddRange
wywołaniaListViewNativeItemCollection.AddRange
, od którego zacząłem audytListViewNativeItemCollection.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” poInsertItems
wywołaniu (oba są chronione czekiemowner.IsHandleCreated
, a właściciel jestListView
), a następnie wywołujeBeginUpdate
.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 doListView.InsertItems
(z linii: 12952), drugie wywołanie (przezEndUpdate
) inne przejście, w którym są one dodawane do aHashTable
, a aDebug.Assert(!listItemsTable.ContainsKey(ItemId))
spowolni je dalej w trybie debugowania. Jeśli uchwyt nie został utworzony, dodaje elementy doArrayList
,listItemsArray
aleif (IsHandleCreated)
następnie wywołujeListView.InsertItemsNative
(z linii: 3848) końcowe przejście przez listę, gdzie faktycznie jest dodawane do natywnego widoku listy. aDebug.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.
OnHandleCreated
Metoda bierzelistItemsArray
i nazywaInsertItemsNative
się bezpośrednio bez wszystkich dodatkowych kłopotów.Możesz sam przeczytać
ListView
kod 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 :)
źródło
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ż
GenerateMember
na false dla każdej kolumny.Link do niestandardowego sortownika widoku listy: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter
źródło
Mam ten sam problem. Potem stwierdziłem, że
sorter
robi to tak wolno. Ustaw sortownik jako zerowythis.listViewAbnormalList.ListViewItemSorter = null;
następnie, gdy sortownik kliknięć, w
ListView_ColumnClick
metodzie, zrób tolv.ListViewItemSorter = new ListViewColumnSorter()
W końcu, po posortowaniu,
sorter
ponownie ustaw wartość null((System.Windows.Forms.ListView)sender).Sort(); lv.ListViewItemSorter = null;
źródło
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.
źródło
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 );
źródło