¿Cómo acelerar la adición de elementos a ListView?

83

Estoy agregando algunos miles (por ejemplo, 53,709) elementos a un ListView de WinForms.

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 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 sigue siendo un orden de magnitud demasiado lento. Separemos la creación de ListViewItems de la adición de ListViewItems, para que encontremos al culpable real:

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 agregar los elementos. Intentemos convertirlo en en AddRangelugar de unforeach

Intento 4: 2,182 ms

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

Un poco mejor. Asegurémonos de que el cuello de botella no esté en elToArray()

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 agregar elementos a la vista de lista. Tal vez la otra sobrecarga de AddRange, donde agregamos un en ListView.ListViewItemCollectionlugar 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 de estirar:

  • Paso 1 : asegúrese de que ninguna columna esté configurada en "ancho automático" :

    ingrese la descripción de la imagen aquí

    Cheque

  • Paso 2 : asegúrese de que ListView no intente ordenar los elementos cada vez que agregue uno:

    ingrese la descripción de la imagen aquí

    Cheque

  • Paso 3 - Pregunte a stackoverflow:

    ingrese la descripción de la imagen aquí

    Cheque

Nota: Obviamente, este ListView no está en modo virtual; ya que no puede "agregar" elementos a una vista de lista virtual (establece el VirtualListSize). Afortunadamente, mi pregunta no se trata de una vista de lista en modo virtual.

¿Hay algo que me falte que pueda explicar el hecho de que agregar elementos a la vista de lista sea tan lento?


Chatter adicional

Sé que la clase ListView de Windows puede funcionar 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 en comparación con el código C # equivalente 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 del contenedor de WinForms ListView me falta?

Ian Boyd
fuente
2
Nota al margen: si usa casillas de verificación, debe establecer el estado marcado antes de agregar a ListView. Inicializando estados comprobados blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx
Tim Schmelter
3
Tengo que preguntar: ¿por qué están agregando TANTOS elementos?
OO
4
Gran pregunta Ian. ¿Has visto este blog sobre el tema? virtualdub.org/blog/pivot/entry.php?id=273
Chris Shain
2
1.349 ms, imposible. Intento con 53709 elementos que me lleva unos minutos. ¿Por qué utilizar una vista de lista con tantos elementos? , no es realmente utilizable. Puedes usar listBox o comboBox para aumentar la velocidad, pero es un número
increíble
4
¿Por qué no utilizar la Vista de lista virtual? De acuerdo, necesita programar cómo recuperar datos de elementos y es posible que tenga que mantener otras cosas como ordenar, filtrar, etc., pero completará la vista de lista instantáneamente sin importar cuántos elementos.
Casperah

Respuestas:

22

Eché un vistazo al código fuente de la vista de lista y noté algunas cosas que pueden hacer que el rendimiento se ralentice en un factor de 4 o más, lo que está viendo:

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

ListViewNativeItemCollection.AddRange(de la línea: 18120) tiene dos pasadas a través de toda la colección de valores, una para recopilar todos los elementos marcados y otra para 'restaurarlos' después de que InsertItemsse llame (ambos están protegidos por un control contraowner.IsHandleCreated , el propietario es ListView) y luego llama BeginUpdate.

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

ListView.InsertItems(de la línea: 12952), la segunda llamada (a través de EndUpdate) otro pase a través de donde se agregan a a HashTable, y a Debug.Assert(!listItemsTable.ContainsKey(ItemId))lo ralentizará aún más en el modo de depuración. Si no se crea el identificador, agrega los elementos a un ArrayList, listItemsArraypero if (IsHandleCreated), luego llama

ListView.InsertItemsNative(de la línea: 3848) paso final a través de la lista donde realmente se agrega a la vista de lista nativa. a Debug.Assert(this.Items.Contains(li)además ralentizará el rendimiento en modo de depuración.

Por lo tanto, hay MUCHAS pasadas adicionales a través de la lista completa de elementos en el control .net antes de que llegue a insertar realmente los elementos en la vista de lista nativa. Algunos de los pases están protegidos por comprobaciones contra el identificador que se está creando, por lo que si puede agregar elementos antes de que se cree el identificador, puede ahorrarle algo de tiempo. El OnHandleCreatedmétodo toma las listItemsArrayllamadas y InsertItemsNativedirectamente sin ningún problema adicional.

Puede leer el ListViewcódigo en la fuente de referencia usted mismo y echar un vistazo, tal vez me perdí algo.

En el número de marzo de 2006 de MSDN Magazine había un artículo tituladoWinning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps .

Este artículo contiene sugerencias 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 procese 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 obtenga lo mejor de ambos mundos.

Editar: probé esta hipótesis de varias maneras, y si bien agregar los elementos antes de crear el identificador es súper rápido, es exponencialmente más lento cuando se crea el identificador. Jugué tratando de engañarlo para crear el identificador, y luego de alguna manera hacer que llame a InsertItemsNative sin pasar por todos los pases adicionales, pero por desgracia, me han frustrado. Lo único que podría pensar que podría ser posible es crear su ListView de Win32 en un proyecto de c ++, rellenarlo con elementos y usar el enlace para capturar el mensaje CreateWindow enviado por ListView al crear su identificador y devolver una referencia a win32 ListView en lugar de una nueva ventana ... pero quién sabe qué efectos secundarios tendría ... un gurú de Win32 tendría que hablar sobre esa loca idea :)

Erikest
fuente
10

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

Yo también he puesto GenerateMember falso para cada columna.

Enlace al clasificador de vista de lista personalizada: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

Slav2
fuente
4
Sí, tener un clasificador activo mientras se agregan elementos es *** tremendamente *** lento. Pero en este caso no tengo clasificador. Pero este es un primer paso útil para las personas que quizás no se den cuenta de que la vista de lista de .NET llama al ordenamiento cada vez que se agrega un elemento, en lugar de al final.
Ian Boyd
0

Tengo el mismo problema. Luego descubrí que sorterlo hacía muy lento. Hacer que el clasificador sea nulo

this.listViewAbnormalList.ListViewItemSorter = null;

luego, cuando haga clic en clasificador, en el ListView_ColumnClickmétodo, hágalo

 lv.ListViewItemSorter = new ListViewColumnSorter()

Por fin, después de que se haya ordenado, sortervuelva a hacer el nulo

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
Batur
fuente
Slav2 sugirió eso . Lo que también sugerí en mi pregunta original.
Ian Boyd
Sí, es lo mismo que la respuesta anterior. :)
Batur
-1

Agregar cuadro ListView

Este es un código simple que pude construir para agregar elementos a un cuadro de lista que consta de columnas. La primera columna es el artículo mientras que la segunda columna es el precio. El siguiente código imprime Item Cinnamon en la primera columna y 0.50 en la segunda columna.

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

No se necesita instanciación.

Demetre Phipps
fuente
Esa es una manera fácil de agregar un elemento. Pero no es una forma rápida de agregar 75.000 elementos.
Ian Boyd
Estoy de acuerdo. Publicaré una implementación para agregar múltiples resultados con la instanciación de esa clase ListView.
Demetre Phipps
-2

Crea todos tus ListViewItems PRIMERA , a continuación, añadirlos a la 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 );
ahaza
fuente