2012-01-25 13 views
70

estoy agregando algunos miles (por ejemplo, 53,709) elementos a un ListView de WinForms.¿Cómo acelerar la adición de elementos a un ListView?

Intento 1: 13,870 ms

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

Esto funciona muy mal. La primera solución obvia es llamar al BeginUpdate/EndUpdate.

Intento 2: 3,106 ms

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

Esto es mejor, pero todavía un orden de magnitud demasiado lento. Vamos creación separada de ListViewItems de añadir ListViewItems, por lo que encontrar el verdadero culpable:

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

El verdadero cuello de botella es la adición de los artículos. Vamos a tratar de convertirlo en AddRange en lugar de un intento foreach

4:2,182 ms

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

un poco mejor. Vamos a estar seguros de que el cuello de botella no está en la ToArray()

Intento 5:2,132 ms

ListViewItem[] arr = items.ToArray(); 

stopwatch.Start(); 

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

stopwatch.Stop(); 

La limitación parece ser la adición de elementos a la vista de lista. Tal vez la otra sobrecarga del AddRange, donde se añade un ListView.ListViewItemCollection en lugar de una matriz

Intento 6:2,141 ms

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

Bueno, eso no es mejor.

Ahora es el momento para estirar:

  • Paso 1 - asegurarse de que ninguna columna se establece en "auto-anchura":

    enter image description here

    Comprobar

  • Paso 2 - asegurarse de que el ListView no está tratando de ordenar los elementos cada vez que la tuya:

    enter image description here

    Comprobar

  • Paso 3 - Haz stackoverflow:

    enter image description here

    Comprobar

Nota: Obviamente, esto no es ListView en modo virtual; ya que no puede/no puede "agregar" elementos a una vista de lista virtual (configura el VirtualListSize). Afortunadamente, mi pregunta no es acerca de una vista de lista en modo virtual.

¿Hay algo que me falta que pueda explicar que la adición de elementos a la lista vista sea tan lenta?


Bono Chatter

sé la clase ListView Windows puede hacerlo mejor, porque puedo escribir código que lo hace en 394 ms:

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

que cuando se compara con el equivalente C# código 1,349 ms:

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

es un orden de magnitud más rápido.

¿Qué propiedad de WinForms ListView wrapper me falta?

+2

Nota al margen: si utiliza Casillas de verificación, debe establecer el estado verificado antes de agregarlo a ListView. Inicializar los estados comprobados http://blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx –

+3

Tengo que preguntar: ¿por qué agregas ESO muchos elementos? –

+3

Gran pregunta Ian. ¿Has visto este blog sobre el tema? http://www.virtualdub.org/blog/pivot/entry.php?id=273 –

Respuesta

20

Me tomó un vistazo al código fuente de la vista de lista y me di cuenta de algunas cosas que pueden hacer que el rendimiento más lento por el factor de 4 o así que estás viendo:

en ListView.cs, ListViewItemsCollection.AddRange llama ListViewNativeItemCollection.AddRange, que es donde empecé mi auditoría

ListViewNativeItemCollection.AddRange (de la línea: 18120) tiene dos pasadas a través de toda la colección de valores, uno para recoger todos los elementos marcados otro a 'restaurar' ellos después InsertItems es llamado (ambos están protegidos por un cheque contra owner.IsHandleCreated, el propietario es el ListView) y luego llama al BeginUpdate.

ListView.InsertItems (de la línea: 12952), primera llamada, tiene otra poligonal de la lista completa, luego se llama ArrayList.AddRange (probablemente otro pase) y luego otro pase después de eso. El llevar a

ListView.InsertItems (de la línea: 12 952), segunda llamada (a través de EndUpdate) otro pase a través de donde se añaden a una HashTable, y una Debug.Assert(!listItemsTable.ContainsKey(ItemId)) retrasará aún más en el modo de depuración. Si no se crea el mango, que añade los elementos a un ArrayList, pero listItemsArrayif (IsHandleCreated), entonces se llama

ListView.InsertItemsNative (de la línea: 3848) pase final a través de la lista, donde en realidad se agrega a la vista de lista nativa. un Debug.Assert(this.Items.Contains(li) reducirá aún más el rendimiento en el modo de depuración.

Así que hay MUCHOS pasos adicionales a través de toda la lista de elementos en el control .net antes de que realmente llegue a insertar los elementos en la vista de lista nativa. Algunos de los pases están protegidos por controles contra el Mango que se está creando, por lo que si puede agregar elementos antes de que se cree el asa, puede ahorrarle algo de tiempo. El método OnHandleCreated toma el listItemsArray y llama al InsertItemsNative directamente sin todo el alboroto adicional.

Puede leer el código ListView en el usted mismo y echar un vistazo, tal vez me perdí algo.

In the March 2006 issue of MSDN Magazine había un artículo llamado Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

Este artículo contiene consejos para mejorar el rendimiento de ListViews, entre otras cosas. Parece indicar que es más rápido agregar elementos antes de que se cree el identificador, pero que pagará un precio cuando se represente el control. Quizás aplicar las optimizaciones de representación mencionadas en los comentarios y agregar los elementos antes de que se cree el identificador obtendrá lo mejor de ambos mundos.

Editar: Probó esta hipótesis de varias formas, y al agregar los elementos antes de crear el asa es suuupermente rápida, es exponencialmente más lenta cuando va a crear el asa. Jugué tratando de engañarlo para crear el identificador, y de alguna manera hice que llamara a InsertItemsNative sin pasar por todos los pases adicionales, pero desafortunadamente me he visto frustrado. Lo único que podría pensar que podría ser posible es crear su Win32 ListView en un proyecto de C++, rellenarlo con elementos y usar el enganche para capturar el mensaje CreateWindow enviado por el ListView al crear su identificador y devolver una referencia al win32 ListView en lugar de una nueva ventana .. pero quién sabe lo que afecta a la parte no habría ... un gurú Win32 tendría que hablar sobre esa idea loca :)

-2

crear todos sus ListViewItemsPRIMERA, a continuación, agréguelos al ListView todos a la vez.

Por ejemplo:

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

Ya lo hizo en los intentos 3, 4, 5 y 6. –

6

He utilizado este código:

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

he puesto también GenerateMember en false para cada columna.

Enlace a la lista personalizada vista Clasificador: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

+4

Sí, tener un * sorter * activo al agregar elementos es *** tremendamente lento. Pero en este caso no tengo un clasificador. Pero este es un primer paso útil para las personas que quizás no se den cuenta de que la vista de lista .NET llama al género * cada vez que se agrega un elemento, en lugar de al final. –

0

Tengo el mismo problema. Luego descubrí que es sorter que es muy lento. Hacer el clasificador como nulo

this.listViewAbnormalList.ListViewItemSorter = null; 

a continuación, cuando haga clic en Clasificador, en ListView_ColumnClick método, hacen que sea

lv.ListViewItemSorter = new ListViewColumnSorter() 

Por fin, después de haber sido ordenadas, hacen que el nulo sorter nuevo

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

[Slav2 sugirió que] (http://stackoverflow.com/a/11379077/12597). Lo cual también sugerí en mi pregunta original. –

+0

Sí, es lo mismo a la respuesta anterior. :) – Batur

Cuestiones relacionadas