Wie kann das Hinzufügen von Elementen zu einer ListView beschleunigt werden?

83

Ich füge ein paar tausend (zB 53.709) Elemente zu einer WinForms ListView hinzu.

Versuch 1 :13,870 ms

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

Das läuft sehr schlecht. Die naheliegende erste Lösung besteht darin, anzurufen BeginUpdate/EndUpdate.

Versuch 2 :3,106 ms

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

Das ist besser, aber immer noch eine Größenordnung zu langsam. Lassen Sie uns die Erstellung von ListViewItems vom Hinzufügen von ListViewItems trennen, damit wir den tatsächlichen Schuldigen finden:

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

Der eigentliche Engpass ist das Hinzufügen der Elemente. Versuchen wir, es in a AddRangeanstatt in a zu konvertierenforeach

Versuch 4: 2,182 ms

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

Ein bisschen besser. Stellen wir sicher, dass der Engpass nicht in derToArray()

Versuch 5: 2,132 ms

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

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

stopwatch.Stop();

Die Einschränkung scheint darin zu bestehen, der Listenansicht Elemente hinzuzufügen. Vielleicht die andere Überladung von AddRange, bei der wir ListView.ListViewItemCollectioneher ein als ein Array hinzufügen

Versuch 6: 2,141 ms

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

Nun, das ist nicht besser.

Jetzt ist es Zeit sich zu strecken:

  • Schritt 1 - Stellen Sie sicher, dass keine Spalte auf "automatische Breite" eingestellt ist :

    Geben Sie hier die Bildbeschreibung ein

    Prüfen

  • Schritt 2 - Stellen Sie sicher, dass ListView nicht jedes Mal, wenn ich eines hinzufüge, versucht, die Elemente zu sortieren:

    Geben Sie hier die Bildbeschreibung ein

    Prüfen

  • Schritt 3 - Stackoverflow fragen:

    Geben Sie hier die Bildbeschreibung ein

    Prüfen

Hinweis: Diese ListView befindet sich offensichtlich nicht im virtuellen Modus. da Sie einer virtuellen Listenansicht keine Elemente hinzufügen können / können (Sie legen die fest VirtualListSize). Zum Glück geht es bei meiner Frage nicht um eine Listenansicht im virtuellen Modus.

Fehlt mir etwas, das dafür verantwortlich sein könnte, dass das Hinzufügen von Elementen zur Listenansicht so langsam ist?


Bonus Chatter

Ich weiß, dass die Windows ListView-Klasse es besser kann, weil ich Code schreiben kann, der dies tut 394 ms:

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

was im Vergleich zum entsprechenden C # -Code 1,349 ms:

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

ist eine Größenordnung schneller.

Welche Eigenschaft des WinForms ListView-Wrappers fehlt mir?

Ian Boyd
quelle
2
Randnotiz: Wenn Sie Kontrollkästchen verwenden, sollten Sie den Status "Aktiviert" festlegen, bevor Sie ihn zur ListView hinzufügen. Initialisierung der überprüften Zustände blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx
Tim Schmelter
3
Ich muss fragen: Warum fügst du so viele Artikel hinzu?
OO
4
Gute Frage Ian. Hast du diesen Blog zu diesem Thema gesehen? virtualdub.org/blog/pivot/entry.php?id=273
Chris Shain
2
1.349 ms, unmöglich. Ich versuche es mit 53709 Artikeln, es dauert einige Minuten. Warum eine Listenansicht mit so vielen Elementen verwenden? ist nicht wirklich verwendbar. Sie können listBox oder comboBox verwenden, um die Geschwindigkeit zu erhöhen, aber es ist eine verrückte Zahl
Xilmiki
4
Warum nicht die virtuelle Listenansicht verwenden? Okay, Sie müssen programmieren, wie Artikeldaten abgerufen werden, und Sie müssen möglicherweise andere Dinge wie Sortieren, Filtern usw. beibehalten, aber Sie werden die Listenansicht sofort füllen, egal wie viele Artikel.
Casperah

Antworten:

21

Ich habe mir den Quellcode für die Listenansicht angesehen und einige Dinge bemerkt, die die Leistung möglicherweise um den Faktor 4 verlangsamen, so dass Sie Folgendes sehen:

ListViewItemsCollection.AddRangeruft in ListView.cs auf, ListViewNativeItemCollection.AddRangewo ich meine Prüfung begonnen habe

ListViewNativeItemCollection.AddRange(aus Zeile: 18120) hat zwei Durchgänge durch die gesamte Sammlung von Werten, einen, um alle markierten Elemente zu sammeln, einen anderen, um sie nach dem InsertItemsAufruf wiederherzustellen (beide werden durch eine Prüfung geschützt owner.IsHandleCreated, wobei der Eigentümer der ist ListView), und ruft dann auf BeginUpdate.

ListView.InsertItems(aus Zeile: 12952), erster Aufruf, hat einen weiteren Durchlauf der gesamten Liste, dann wird ArrayList.AddRange aufgerufen (wahrscheinlich ein weiterer Durchgang dort) und danach ein weiterer Durchgang. Führen zu

ListView.InsertItems(ab Zeile: 12952), zweiter Aufruf (über EndUpdate) einen weiteren Durchgang, bei dem sie zu a hinzugefügt werden HashTable, und a Debug.Assert(!listItemsTable.ContainsKey(ItemId))verlangsamt ihn im Debug-Modus weiter. Wenn das Handle nicht erstellt wird, werden die Elemente zu einem ArrayList, listItemsArrayaber hinzugefügtif (IsHandleCreated) dann wird es aufgerufen

ListView.InsertItemsNative(ab Zeile: 3848) Endgültiger Durchlauf durch die Liste, wo sie tatsächlich zur nativen Listenansicht hinzugefügt wird. einDebug.Assert(this.Items.Contains(li) verlangsamt zusätzlich die Leistung im Debug-Modus.

Es gibt also VIELE zusätzliche Durchgänge durch die gesamte Liste der Elemente im .net-Steuerelement, bevor die Elemente tatsächlich in die native Listenansicht eingefügt werden. Einige der Durchgänge werden durch Überprüfungen des erstellten Handles geschützt. Wenn Sie also Elemente hinzufügen können, bevor das Handle erstellt wird, sparen Sie möglicherweise Zeit. Die OnHandleCreatedMethode nimmt die listItemsArrayund AufrufeInsertItemsNative direkt ohne all den zusätzlichen Aufwand auf.

Sie können den ListViewCode in der Referenzquelle lesen selbst und einen Blick darauf werfen, vielleicht habe ich etwas verpasst.

In der März-Ausgabe 2006 des MSDN Magazine gab es einen Artikel mit dem TitelWinning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps .

Dieser Artikel enthielt unter anderem Tipps zur Verbesserung der Leistung von ListViews. Es scheint darauf hinzudeuten, dass das Hinzufügen von Elementen vor dem Erstellen des Handles schneller ist, Sie jedoch beim Rendern des Steuerelements einen Preis zahlen. Wenn Sie die in den Kommentaren genannten Rendering-Optimierungen anwenden und die Elemente hinzufügen, bevor das Handle erstellt wird, erhalten Sie möglicherweise das Beste aus beiden Welten.

Bearbeiten: Diese Hypothese wurde auf verschiedene Weise getestet. Das Hinzufügen der Elemente vor dem Erstellen des Handles ist zwar superschnell, beim Erstellen des Handles jedoch exponentiell langsamer. Ich habe versucht, es auszutricksen, um das Handle zu erstellen, und es dann irgendwie dazu gebracht, InsertItemsNative aufzurufen, ohne alle zusätzlichen Durchgänge durchzugehen, aber leider wurde ich vereitelt. Das einzige, was ich für möglich halten könnte, ist, Ihre Win32-ListView in einem C ++ - Projekt zu erstellen, sie mit Elementen zu füllen und mithilfe von Hooking die von ListView beim Erstellen des Handles gesendete CreateWindow-Nachricht zu erfassen und einen Verweis auf win32 zurückzugeben ListView anstelle eines neuen Fensters ... aber wer weiß, was die Seite dort beeinflusst ... ein Win32-Guru müsste über diese verrückte Idee sprechen :)

Erikest
quelle
10

Ich habe diesen Code verwendet:

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

Ich habe auch eingestellt GenerateMember für jede Spalte auf false gesetzt.

Link zum Sortierer für benutzerdefinierte Listenansichten: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

Slav2
quelle
4
Ja, wenn ein Sortierer beim Hinzufügen von Elementen aktiv ist, ist das unglaublich langsam. Aber in diesem Fall habe ich keinen Sortierer. Dies ist jedoch ein nützlicher erster Schritt für Personen, die möglicherweise nicht erkennen, dass die .NET-Listenansicht die Sortierung jedes Mal aufruft, wenn ein Element hinzugefügt wird - und nicht am Ende.
Ian Boyd
0

Ich habe das gleiche Problem. Dann fand ich, sorterdass es so langsam ist. Machen Sie den Sortierer als null

this.listViewAbnormalList.ListViewItemSorter = null;

Wenn Sie dann auf Sortierer klicken ListView_ColumnClick, machen Sie es auf Methode

 lv.ListViewItemSorter = new ListViewColumnSorter()

Nachdem es sortiert wurde, machen Sie endlich wieder die sorterNull

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
Batur
quelle
Slav2 schlug das vor . Was ich auch in meiner ursprünglichen Frage vorgeschlagen habe.
Ian Boyd
Ja, die Antwort oben ist genauso. :)
Batur
-1

ListView Box Hinzufügen

Dies ist ein einfacher Code, den ich erstellen konnte, um Elemente zu einer Listbox hinzuzufügen, die aus Spalten besteht. Die erste Spalte ist Artikel, während die zweite Spalte Preis ist. Der folgende Code gibt Item Cinnamon in der ersten Spalte und 0,50 in der zweiten Spalte aus.

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

Keine Instanziierung erforderlich.

Demetre Phipps
quelle
Das ist eine einfache Möglichkeit, einen Artikel hinzuzufügen. Es ist jedoch keine schnelle Möglichkeit, 75.000 Elemente hinzuzufügen.
Ian Boyd
Genau. Ich werde eine Implementierung zum Hinzufügen mehrerer Ergebnisse veröffentlichen, indem ich diese ListView-Klasse instanziiere.
Demetre Phipps
-2

Erstellen Sie alle Ihre Listviewitem FIRST , dann fügen Sie sie in den Listview auf einmal.

Beispielsweise:

    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 );
Ahasja
quelle