Frage Wie beschleunigt man das Hinzufügen von Elementen zu einem ListView?


Ich füge einige tausend (z. B. 53.709) Elemente zu einem 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 offensichtliche erste Lösung ist ein Anruf 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, so dass wir den eigentlichen 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 Flaschenhals ist das Hinzufügen der Elemente. Lassen Sie uns versuchen, es zu konvertieren AddRange eher als ein foreach

Versuch 4:  2,182 ms

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

Ein bisschen besser. Lassen Sie uns sicher sein, dass der Engpass nicht in der ToArray()

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 Elemente zur Listenansicht hinzuzufügen. Vielleicht die andere Überladung von AddRange, wo wir ein hinzufügen ListView.ListViewItemCollection anstatt ein Array

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 zu strecken:

  • Schritt 1 - Stellen Sie sicher, dass für keine Spalte festgelegt ist "automatische Breite":

    enter image description here

    Prüfen

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

    enter image description here

    Prüfen

  • Schritt 3 - Fragen Sie stackoverflow:

    enter image description here

    Prüfen

Hinweis: Offensichtlich ist diese ListView nicht im virtuellen Modus; Da Sie Objekte nicht zu einer virtuellen Listenansicht hinzufügen / hinzufügen können, legen Sie die VirtualListSize). Glücklicherweise geht es bei meiner Frage nicht um eine Listenansicht im virtuellen Modus.

Gibt es etwas, das ich vermisse, das für das Hinzufügen von Elementen zur Listenansicht verantwortlich sein könnte, die so langsam ist?


Bonus-Chatter

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

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

was verglichen mit dem äquivalenten 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?


75
2018-01-25 18:41


Ursprung


Antworten:


Ich habe mir den Quellcode für die Listenansicht angeschaut und einige Dinge bemerkt, die die Leistung um den Faktor 4 verlangsamen könnten.

in ListView.cs, ListViewItemsCollection.AddRange Anrufe ListViewNativeItemCollection.AddRange, wo ich meine Prüfung begonnen habe

ListViewNativeItemCollection.AddRange (aus Zeile: 18120) hat zwei Durchläufe durch die gesamte Sammlung von Werten, einen, um alle überprüften Elemente zu sammeln, und einen anderen, um sie danach wiederherzustellen InsertItems heißt (sie werden beide durch einen Scheck gegen owner.IsHandleCreatedBesitzer ist der ListView) ruft dann an BeginUpdate.

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

ListView.InsertItems (aus Zeile: 12952), zweiter Aufruf (über EndUpdate) ein weiterer Durchgang, wo sie zu a hinzugefügt werden HashTable, und ein Debug.Assert(!listItemsTable.ContainsKey(ItemId)) wird es im Debug-Modus weiter verlangsamen. Wenn das Handle nicht erstellt wird, fügt es die Elemente zu einem hinzu ArrayList, listItemsArray aber if (IsHandleCreated), dann ruft es an

ListView.InsertItemsNative (aus Zeile: 3848) Endgültig durch die Liste gehen, wo sie tatsächlich zur nativen Listenansicht hinzugefügt wird. ein Debug.Assert(this.Items.Contains(li) wird die Leistung im Debug-Modus zusätzlich verlangsamen.

Es gibt also eine Menge zusätzlicher Durchläufe durch die gesamte Liste der Elemente in der .net-Steuerelement, bevor es tatsächlich tatsächlich die Elemente in die systemeigene Listenansicht eingefügt wird. Einige der Durchgänge werden durch Überprüfungen gegen das erstellte Handle geschützt. Wenn Sie also Elemente hinzufügen können, bevor das Handle erstellt wird, können Sie möglicherweise etwas Zeit sparen. Das OnHandleCreatedMethode dauert die listItemsArray und Anrufe InsertItemsNative direkt ohne all den zusätzlichen Aufwand.

Sie können das lesen ListView Code in der Referenzquelle selbst und guck mal, vielleicht habe ich etwas verpasst.

In der März 2006 Ausgabe des MSDN Magazins Dort wurde ein Artikel genannt Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

Dieser Artikel enthielt unter anderem Tipps zur Verbesserung der Performance von ListViews. Es scheint, dass es schneller ist, Elemente hinzuzufügen, bevor das Handle erstellt wird, aber dass Sie einen Preis zahlen, wenn das Steuerelement gerendert wird. Wenn Sie die in den Kommentaren erwähnten Rendering-Optimierungen anwenden und die Elemente hinzufügen, bevor das Handle erstellt wird, erhalten Sie das Beste aus beiden Welten.

Bearbeiten: Getestet diese Hypothese in einer Vielzahl von Möglichkeiten, und während das Hinzufügen der Elemente vor dem Erstellen des Handle ist suuuper schnell, ist es exponentiell langsamer, wenn es um das Handle zu erstellen geht. Ich habe mit dem Versuch gespielt, es auszutricksen, um das Handle zu erstellen, dann habe ich es irgendwie geschafft, InsertItemsNative aufzurufen, ohne alle zusätzlichen Pässe zu durchlaufen, aber ach, ich wurde durchkreuzt. Die einzige Sache, die ich vielleicht denken könnte, ist, Ihre Win32 ListView in einem C ++ - Projekt zu erstellen, es mit Elementen stopfen und Hooking verwenden, um die CreateWindow Nachricht von der ListView beim Erstellen der Handle und eine Referenz an die win32 zurückgeben zu erfassen ListView statt eines neuen Fensters .. aber wer weiß, was die Seite betrifft, wäre ... ein Win32-Guru müsste über diese verrückte Idee sprechen :)


21
2018-01-26 08:27



Ich habe diesen Code benutzt:

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 festgelegt GenerateMember für jede Spalte falsch.

Link zum Sortierer der benutzerdefinierten Listenansicht: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter


7
2017-07-07 22:16



Ich habe das gleiche Problem. Dann habe ich es gefunden sorter Mach es so langsam. Stellen Sie den Sortierer als null ein

this.listViewAbnormalList.ListViewItemSorter = null;

dann, wenn Sortierer klicken, auf ListView_ColumnClick Methode, mach es

 lv.ListViewItemSorter = new ListViewColumnSorter()

Endlich, nachdem es sortiert wurde, mach das sorter null wieder

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;

0
2018-05-18 06:26



Erstellen Sie alle Ihre ListViewItems  ZUERST, dann fügst du sie hinzu Listenansicht alles 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 );

-2
2018-01-27 22:30