87 votes

Comment accélérer l'ajout d'éléments dans une ListView ?

J'ajoute quelques milliers (par exemple 53 709) d'é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 est d'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 toujours un ordre de grandeur trop lent. Séparons la création de ListViewItems de l'ajout de ListViewItems, afin de trouver le vrai 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 articles. Essayons de le convertir en AddRange plutôt qu'un foreach

Tentative 4 : 2,182 ms

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

Un peu mieux. Soyons sûrs que le goulot d'étranglement n'est pas dans le 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 l'ajout d'éléments dans le listview. Peut-être que 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();

Ce n'est pas mieux.

Maintenant, il est temps de s'étirer :

  • Étape 1 - assurez-vous qu'aucune colonne n'est définie comme "auto-width" :

    enter image description here

    Vérifiez

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

    enter image description here

    Vérifiez

  • Étape 3 - Demandez à stackoverflow :

    enter image description here

    Vérifiez

Nota: De toute évidence, cette liste n'est pas en mode virtuel ; puisque vous ne pouvez pas "ajouter" d'éléments à une liste virtuelle (vous définissez l'attribut VirtualListSize ). Heureusement, ma question ne porte pas sur une vue de liste en mode virtuel.

Y a-t-il quelque chose qui m'échappe et qui pourrait expliquer que l'ajout d'éléments à la liste soit si lent ?


Bonus Chatter

je sais que la classe ListView de Windows 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, lorsqu'il est comparé 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 ListView de WinForms me manque-t-il ?

2 votes

Remarque : si vous utilisez des cases à cocher, vous devez définir l'état coché avant de les ajouter à la liste. Initialisation des états cochés blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx

3 votes

Je dois demander : pourquoi ajoutez-vous autant d'articles ?

4 votes

Bonne question Ian. Avez-vous vu ce blog sur le sujet ? virtualdub.org/blog/pivot/entry.php?id=273

22voto

Erikest Points 1493

J'ai jeté un coup d'œil au code source de la vue en liste et j'ai remarqué quelques éléments susceptibles de ralentir les performances par un facteur de 4 ou plus, comme vous le constatez :

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

ListViewNativeItemCollection.AddRange (de la ligne : 18120) passe deux fois par toute la collection de valeurs, l'une pour collecter tous les éléments cochés, l'autre pour les 'restaurer' après InsertItems est appelé (ils sont tous les deux protégés par une vérification de la présence de owner.IsHandleCreated le propriétaire étant le ListView ), puis appelle BeginUpdate .

ListView.InsertItems (à partir de la ligne : 12952), premier appel, il y a une autre traversée de la liste entière, puis ArrayList.AddRange est appelé (probablement une autre passe ici), puis une autre passe après cela. Ce qui mène à

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

ListView.InsertItemsNative (de la ligne : 3848) dernier passage dans la liste où elle est réellement ajoutée à la liste native. a Debug.Assert(this.Items.Contains(li) ralentira en outre les performances en mode débogage.

Il y a donc BEAUCOUP de passages supplémentaires à travers la liste entière d'éléments dans le contrôle .net avant de pouvoir insérer les éléments dans la liste native. Certains de ces passages sont protégés par des contrôles sur le Handle en cours de création, donc si vous pouvez ajouter des éléments avant la création du handle, cela peut vous faire gagner du temps. Le site OnHandleCreated La méthode prend le listItemsArray et appelle InsertItemsNative directement sans toutes les complications supplémentaires.

Vous pouvez lire le ListView dans le source de référence vous-même et jetez un coup d'oeil, peut-être que j'ai manqué quelque chose.

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

Cet article contenait des conseils pour améliorer les performances des ListViews, entre autres choses. Il semble indiquer qu'il est plus rapide d'ajouter des éléments avant la création du handle, mais que vous en payez le prix lors du rendu du contrôle. 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 handle, vous obtiendrez le meilleur des deux mondes.

Edit : J'ai testé cette hypothèse de différentes manières, et alors que l'ajout des éléments avant la création du handle est super rapide, il est exponentiellement plus lent lorsqu'il s'agit de créer le handle. J'ai essayé de l'inciter à créer la poignée, puis à appeler InsertItemsNative sans passer par tous les passages supplémentaires, mais hélas j'ai été contrecarré. La seule chose qui me semble possible est de créer votre ListView Win32 dans un projet c++, de la remplir d'éléments et d'utiliser un hooking pour capturer le message CreateWindow envoyé par la ListView lors de la création de son handle et de renvoyer une référence à la ListView Win32 au lieu d'une nouvelle fenêtre... mais qui sait quels seraient les effets secondaires... il faudrait qu'un gourou Win32 s'exprime sur cette idée folle :)

11voto

Slav2 Points 21

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 aussi mis GenerateMember à false pour chaque colonne.

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

4 votes

Oui, avoir un trieuse actif alors que l'ajout d'éléments est *** terriblement*** lent. Mais dans ce cas, je n'ai pas de trieur. Mais c'est une première étape utile pour les personnes qui ne réalisent pas que le listview .NET appelle le tri. à chaque fois un élément est ajouté - plutôt qu'à la fin.

0voto

Batur Points 341

J'ai le même problème. Puis j'ai découvert que c'est sorter qui le rendent si lent. Rendez le trieur aussi nul

this.listViewAbnormalList.ListViewItemSorter = null;

puis lorsque vous cliquez sur trieur, sur ListView_ColumnClick méthode, faites-le

 lv.ListViewItemSorter = new ListViewColumnSorter()

Enfin, après avoir été trié, faites l'objet d'une demande d'accès à l'information. sorter encore nul

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

0 votes

Slav2 a suggéré que . Ce que j'ai également suggéré dans ma question initiale.

0 votes

Oui, c'est la même chose que la réponse ci-dessus. :)

-1voto

Demetre Phipps Points 59

Boîte ListView Ajouter

Voici un code simple que j'ai pu construire pour ajouter des éléments à une boîte de liste composée de colonnes. La première colonne est l'article tandis que la deuxième colonne est le prix. Le code ci-dessous imprime l'article Cannelle 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'est nécessaire.

0 votes

C'est une facile pour ajouter un élément. Mais ce n'est pas un rapide façon d'ajouter 75 000 articles.

-4voto

ahazzah Points 396

Créez tous vos ListViewItems FIRST puis les ajouter à l ListView en même temps.

Par exemple :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X