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 AddRange
lugar 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.ListViewItemCollection
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 de estirar:
Paso 1 : asegúrese de que ninguna columna esté configurada en "ancho automático" :
Cheque
Paso 2 : asegúrese de que ListView no intente ordenar los elementos cada vez que agregue uno:
Cheque
Paso 3 - Pregunte a stackoverflow:
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?
fuente
Respuestas:
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.AddRange
llamadasListViewNativeItemCollection.AddRange
, que es donde comencé mi auditoríaListViewNativeItemCollection.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 queInsertItems
se llame (ambos están protegidos por un control contraowner.IsHandleCreated
, el propietario esListView
) y luego llamaBeginUpdate
.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 aListView.InsertItems
(de la línea: 12952), la segunda llamada (a través deEndUpdate
) otro pase a través de donde se agregan a aHashTable
, y aDebug.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 unArrayList
,listItemsArray
peroif (IsHandleCreated)
, luego llamaListView.InsertItemsNative
(de la línea: 3848) paso final a través de la lista donde realmente se agrega a la vista de lista nativa. aDebug.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
OnHandleCreated
método toma laslistItemsArray
llamadas yInsertItemsNative
directamente sin ningún problema adicional.Puede leer el
ListView
có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 titulado
Winning 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 :)
fuente
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
fuente
Tengo el mismo problema. Luego descubrí que
sorter
lo hacía muy lento. Hacer que el clasificador sea nulothis.listViewAbnormalList.ListViewItemSorter = null;
luego, cuando haga clic en clasificador, en el
ListView_ColumnClick
método, hágalolv.ListViewItemSorter = new ListViewColumnSorter()
Por fin, después de que se haya ordenado,
sorter
vuelva a hacer el nulo((System.Windows.Forms.ListView)sender).Sort(); lv.ListViewItemSorter = null;
fuente
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.
fuente
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 );
fuente