如何加速向 ListView 添加项?

我在 WinForms ListView 中添加了几千个(例如53,709)条目。

尝试1 : 13,870 ms

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

这种方法运行得非常糟糕,第一个明显的修复方法是调用 BeginUpdate/EndUpdate

尝试2 : 3,106 ms

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

这样好多了但数量级还是太慢了。让我们分别创建 ListViewItems 和添加 ListViewItems,以便找到实际的罪魁祸首:

尝试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()

真正的瓶颈是添加项。让我们尝试将其转换为 AddRange而不是 foreach

尝试4: 2,182 ms

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

好一点。让我们确保瓶颈不在 ToArray()

尝试5: 2,132 ms

ListViewItem[] arr = items.ToArray();


stopwatch.Start();


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


stopwatch.Stop();

限制似乎是向列表视图添加项。也许是 AddRange的另一个重载,我们在其中添加一个 ListView.ListViewItemCollection而不是一个数组

尝试6: 2,141 ms

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

这样也好不到哪里去。

现在是伸展运动的时候了:

  • 步骤1 -确保没有列设置为 “自动宽度”:

    enter image description here

    将军

  • 步骤2 -确保 ListView 不会在我每次添加条目时都尝试对条目进行排序:

    enter image description here

    将军

  • 步骤3 -询问 stackoverflow:

    enter image description here

    将军

注意: 显然这个 ListView 不处于虚拟模式; 因为您不能/不能向虚拟列表视图“添加”项(您设置了 VirtualListSize)。幸运的是,我的问题与虚拟模式中的列表视图无关。

我是否遗漏了什么东西,可以解释为什么向列表视图添加条目的速度如此之慢?


额外的闲聊

我知道 Windows ListView 类可以做得更好,因为我可以在 394 ms中编写这样的代码:

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

与等效的 C # 代码 1,349 ms相比:

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

数量级更快。

我遗漏了 WinFormsListView 包装的哪个属性?

54968 次浏览

I took a look at the source code for the list view and I noticed a few things that may make the performance slow down by the factor of 4 or so that you're seeing:

in ListView.cs, ListViewItemsCollection.AddRange calls ListViewNativeItemCollection.AddRange, which is where I began my audit

ListViewNativeItemCollection.AddRange (from line: 18120) has two passes through the entire collection of values, one to collect all the checked items another to 'restore' them after InsertItems is called (they're both guarded by a check against owner.IsHandleCreated, owner being the ListView) then calls BeginUpdate.

ListView.InsertItems (from line: 12952), first call, has another traverse of the entire list then ArrayList.AddRange is called (probably another pass there) then another pass after that. Leading to

ListView.InsertItems (from line: 12952), second call (via EndUpdate) another pass through where they are added to a HashTable, and a Debug.Assert(!listItemsTable.ContainsKey(ItemId)) will slow it further in debug mode. If the handle isn't created, it adds the items to an ArrayList, listItemsArray but if (IsHandleCreated), then it calls

ListView.InsertItemsNative (from line: 3848) final pass through the list where it is actually added to the native listview. a Debug.Assert(this.Items.Contains(li) will additionally slow down performance in debug mode.

So there are A LOT of extra passes through the entire list of items in the .net control before it ever gets to actually inserting the items into the native listview. Some of the passes are guarded by checks against the Handle being created, so if you can add items before the handle is created, it may save you some time. The OnHandleCreated method takes the listItemsArray and calls InsertItemsNative directly without all the extra fuss.

You can read the ListView code in the reference source yourself and take a look, maybe I missed something.

In the March 2006 issue of MSDN Magazine there was an article called Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

This article contained tips for improving the performance of ListViews, among other things. It seems to indicate that its faster to add items before the handle is created, but that you will pay a price when the control is rendered. Perhaps applying the rendering optimizations mentioned in the comments and adding the items before the handle is created will get the best of both worlds.

Edit: Tested this hypothesis in a variety of ways, and while adding the items before creating the handle is suuuper fast, it is exponentially slower when it goes to create the handle. I played with trying to trick it to create the handle, then somehow get it to call InsertItemsNative without going through all the extra passes, but alas I've been thwarted. The only thing I could think might be possible, is to create your Win32 ListView in a c++ project, stuff it with items, and use hooking to capture the CreateWindow message sent by the ListView when creating its handle and pass back a reference to the win32 ListView instead of a new window.. but who knows what the side affects there would be... a Win32 guru would need to speak up about that crazy idea :)

Create all your ListViewItems FIRST, then add them to the ListView all at once.

For example:

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

I used this 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();

I have set also GenerateMember to false for each column.

Link to custom list view sorter: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

I have the same problem. Then I found it's sorter make it so slow. Make the sorter as null

this.listViewAbnormalList.ListViewItemSorter = null;

then when click sorter, on ListView_ColumnClick method , make it

 lv.ListViewItemSorter = new ListViewColumnSorter()

At last, after it's been sorted, make the sorter null again

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

ListView Box Add

This is simple code I was able to construct to add Items to a listbox that consist of columns. The first column is item while the second column is price. The code below prints Item Cinnamon in first column and 0.50 in the second column.

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

No instantiation needed.