web-dev-qa-db-fra.com

Comment accélérer l'ajout d'éléments à un ListView?

j'ajoute quelques milliers (par exemple 53 709) éléments à une liste WinForms.

Tentative 1 : 13,870 ms

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

Cela fonctionne très mal. La première solution évidente consiste à appeler BeginUpdate/EndUpdate.

Tentative 2 : 3,106 ms

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

C'est mieux, mais c'est quand même un ordre de grandeur trop lent. Séparons la création de ListViewItems de l’ajout de ListViewItems, nous allons donc trouver le coupable:

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

Le véritable goulot d'étranglement est l'ajout des éléments. Essayons de le convertir en AddRange plutôt qu'en foreach

Tentative 4: 2,182 ms

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

Un peu mieux. Soyons sûr que le goulot d'étranglement n'est pas dans la ToArray()

Tentative 5: 2,132 ms

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

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

stopwatch.Stop();

La limitation semble être d'ajouter des éléments à la listview. Peut-être l'autre surcharge de AddRange, où nous ajoutons un ListView.ListViewItemCollection plutôt qu'un tableau

Tentative 6: 2,141 ms

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

Eh bien ce n'est pas mieux.

Maintenant il est temps de s'étirer:

  • Étape 1 - assurez-vous qu'aucune colonne n'est définie sur "largeur automatique":

    enter image description here

    Vérifier

  • Étape 2 - assurez-vous que ListView n'essaie pas de trier les éléments à chaque fois que j'en ajoute un:

    enter image description here

    Vérifier

  • Étape 3 - Demandez à stackoverflow:

    enter image description here

    Vérifier

Remarque: Évidemment, ce ListView n'est pas en mode virtuel; puisque vous ne pouvez/ne pouvez pas "ajouter" d’éléments à une vue de liste virtuelle (vous définissez la variable VirtualListSize). Heureusement, ma question ne concerne pas une vue en liste en mode virtuel.

Y a-t-il quelque chose qui me manque qui pourrait expliquer l'ajout d'éléments à la vue liste si lente?


Bonus Chatter

je sais que la classe Windows ListView peut faire mieux, car je peux écrire du code qui le fait en 394 ms:

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

qui, par rapport au code C # équivalent 1,349 ms:

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

est un ordre de grandeur plus rapide.

Quelle propriété du wrapper WinForms ListView me manque?

78
Ian Boyd

J'ai jeté un coup d'œil au code source de la liste et j'ai remarqué quelques problèmes susceptibles de ralentir les performances de 4 fois environ:

dans ListView.cs, ListViewItemsCollection.AddRange appelle ListViewNativeItemCollection.AddRange, c'est là que j'ai commencé mon audit

ListViewNativeItemCollection.AddRange (à partir de la ligne: 18120) comporte deux passes dans la collection complète de valeurs, une pour collecter tous les éléments cochés, une autre pour les "restaurer" après l'appel de InsertItems (ils sont tous deux protégés par un contrôle contre owner.IsHandleCreated, le propriétaire étant la ListView ) appelle ensuite BeginUpdate.

ListView.InsertItems (à partir de la ligne: 12952), le premier appel, a un autre parcours de la liste entière, puis ArrayList.AddRange est appelé (probablement un autre passage), puis un autre passage après cela. Menant à

ListView.InsertItems (à partir de la ligne: 12952), deuxième appel (via EndUpdate) un autre passage où ils sont ajoutés à un HashTable, et un Debug.Assert(!listItemsTable.ContainsKey(ItemId)) le ralentira davantage en mode débogage. Si le descripteur n'est pas créé, il ajoute les éléments à ArrayList, listItemsArray mais if (IsHandleCreated), puis il appelle

ListView.InsertItemsNative (de la ligne: 3848) final passe à travers la liste où il est réellement ajouté à la listview native. Debug.Assert(this.Items.Contains(li) ralentira en outre les performances en mode débogage.

Donc, il y a BEAUCOUP de passes supplémentaires dans la liste complète des éléments du contrôle .net avant qu'il n'arrive réellement à insérer les éléments dans la vue liste native. Certaines passes sont protégées par des vérifications par rapport à la poignée en cours de création. Par conséquent, si vous pouvez ajouter des éléments avant la création de la poignée, vous gagnerez peut-être un peu de temps. La méthode OnHandleCreated prend la listItemsArray et appelle InsertItemsNative directement sans prise de tête supplémentaire.

Vous pouvez lire vous-même le code ListView dans la source reference et jeter un coup d'œil. Peut-être que quelque chose m'a échappé.

Dans le numéro de mars 2006 de MSDN Magazine , il y avait un article appelé Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

Cet article contenait des astuces pour améliorer les performances de ListViews, entre autres. Cela semble indiquer qu'il est plus rapide d'ajouter des éléments avant la création du descripteur, mais que vous paierez un prix lorsque le contrôle sera rendu. Peut-être qu'en appliquant les optimisations de rendu mentionnées dans les commentaires et en ajoutant les éléments avant la création du descripteur, vous obtiendrez le meilleur des deux mondes.

Edit: a testé cette hypothèse de différentes manières et, bien que l'ajout des éléments avant la création du descripteur soit plus rapide, il est exponentiellement plus lent lors de la création du descripteur. J'ai joué avec en essayant de le tromper pour créer le handle, puis de le faire appeler InsertItemsNative sans passer par toutes les passes supplémentaires, mais hélas, j'ai été contrecarré. La seule chose que je puisse imaginer possible est de créer votre liste Win32 dans un projet c ++, de le farcir avec des éléments et d’utiliser le hook pour capturer le message CreateWindow envoyé par ListView lors de la création de son descripteur et de renvoyer une référence au win32. ListView au lieu d'une nouvelle fenêtre .. mais qui sait ce que le côté affecte il y aurait ... un gourou Win32 aurait besoin de parler de cette idée folle :)

21
Erikest

J'ai utilisé ce code:

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

J'ai également défini GenerateMember sur false pour chaque colonne.

Lien vers le trieur de vue liste personnalisé: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

8
Slav2

ListView Box Ajouter

C’est un code simple que j’ai pu construire pour ajouter des éléments à une zone de liste composée de colonnes. La première colonne est article tandis que la deuxième colonne est le prix. Le code ci-dessous imprime l’article Cinnamon dans la première colonne et 0,50 dans la deuxième colonne. 

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

Aucune instanciation nécessaire. 

0
Demetre Phipps

J'ai le même problème. Puis j'ai trouvé que c'est sorter le rendre si lent . Rendre la trieuse comme nulle

this.listViewAbnormalList.ListViewItemSorter = null;

puis lorsque vous cliquez sur la trieuse, sur la méthode ListView_ColumnClick, rendez-la 

 lv.ListViewItemSorter = new ListViewColumnSorter()

Enfin, après le tri, redéfinissez la variable sorter

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