如何加快向 ListView 添加项目?

Posted

技术标签:

【中文标题】如何加快向 ListView 添加项目?【英文标题】:How to speed adding items to a ListView? 【发布时间】:2012-02-18 23:13:09 【问题描述】:

我正在向 WinForms ListView 添加几千个(例如 53,709 个)项目。

尝试 113,870 ms

foreach (Object o in list)

   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);

这运行非常糟糕。显而易见的第一个解决方法是致电BeginUpdate/EndUpdate

尝试 23,106 ms

listView.BeginUpdate();
foreach (Object o in list)

   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);

listView.EndUpdate();

这更好,但仍然太慢了一个数量级。让我们将 ListViewItems 的创建与添加 ListViewItems 分开,这样我们就找到了真正的罪魁祸首:

尝试 32,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 步 - 确保没有将列设置为 “auto-width”

检查

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

检查

第 3 步 - 询问 ***:

检查

注意:显然这个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();

快了一个数量级。

我缺少 WinForms ListView 包装器的哪些属性?

【问题讨论】:

旁注:如果使用复选框,您应该在添加到 ListView 之前设置选中状态。初始化检查状态blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx 我不得不问:你为什么要添加这么多项目? 伊恩的好问题。你看过这个关于这个主题的博客吗? virtualdub.org/blog/pivot/entry.php?id=273 1,349 毫秒,不可能。我尝试使用 53709 个项目,这需要几分钟。为什么要使用包含这么多项目的列表视图? , 不是真的有用。您可以使用 listBox 或 comboBox 来提高速度,但这是一个疯狂的数字 为什么不使用虚拟列表视图?好的,您需要编程如何检索项目数据,并且您可能需要维护其他事情,例如排序、过滤等,但无论项目有多少,您都会立即填充列表视图。 【参考方案1】:

ListView Box Add

这是我能够构建的简单代码,用于将项目添加到由列组成的列表框中。第一列是商品,第二列是价格。 下面的代码在第一列打印 Item Cinnamon,在第二列打印 0.50。

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

不需要实例化。

【讨论】:

这是一种添加项目的简单方法。但这并不是添加 75,000 个项目的快速方法。【参考方案2】:

我也有同样的问题。然后我发现它是sorter 让它这么慢。 将排序器设为空

this.listViewAbnormalList.ListViewItemSorter = null;

然后当点击排序器时,在ListView_ColumnClick方法上,制作它

 lv.ListViewItemSorter = new ListViewColumnSorter()

最后,排序后,再次将sorter设为null

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

【讨论】:

Slav2 suggested that。我在最初的问题中也提出了这一建议。 是的,和上面的答案一样。 :)【参考方案3】:

我查看了列表视图的源代码,我注意到一些事情可能会使性能降低 4 倍左右,正如您所见:

在 ListView.cs 中,ListViewItemsCollection.AddRange 调用 ListViewNativeItemCollection.AddRange,这是我开始审核的地方

ListViewNativeItemCollection.AddRange(从第 18120 行开始)有两次遍历整个值集合,一次收集所有已检查的项目,另一次在调用 InsertItems 后“恢复”它们(它们都由检查保护针对owner.IsHandleCreated,所有者是ListView) 然后调用BeginUpdate

ListView.InsertItems(从第 12952 行开始),第一次调用,对整个列表进行另一次遍历,然后调用 ArrayList.AddRange(可能是另一次传递),然后再调用一次。导致

ListView.InsertItems(从第 12952 行开始),第二次调用(通过EndUpdate)又一次通过,它们被添加到HashTableDebug.Assert(!listItemsTable.ContainsKey(ItemId)) 将在调试模式下进一步减慢它。如果未创建句柄,则将项目添加到 ArrayListlistItemsArrayif (IsHandleCreated),然后调用

ListView.InsertItemsNative(从第 3848 行开始)最终通过列表,它实际上被添加到本机列表视图中。 Debug.Assert(this.Items.Contains(li) 还会降低调试模式下的性能。

因此,在将项目实际插入本机列表视图之前,.net 控件中的整个项目列表有很多额外的传递。一些通行证受到对正在创建的句柄的检查的保护,因此如果您可以在创建句柄之前添加项目,它可能会为您节省一些时间。 OnHandleCreated 方法采用 listItemsArray 并直接调用 InsertItemsNative ,无需大惊小怪。

您可以自己阅读reference source中的ListView代码并看看,也许我错过了什么。

In the March 2006 issue of MSDN Magazine有一篇文章叫Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps

本文包含提高 ListView 性能的技巧等。这似乎表明在创建句柄之前添加项目更快,但是在呈现控件时您将付出代价。也许应用 cmets 中提到的渲染优化并在创建句柄之前添加项目将获得两全其美。

编辑:以多种方式测试了这个假设,虽然在创建句柄之前添加项目非常快,但在创建句柄时却是指数级的慢。我试图欺骗它来创建句柄,然后以某种方式让它调用 InsertItemsNative 而不经过所有额外的传递,但是我被挫败了。我唯一能想到的可能是在 c++ 项目中创建你的 Win32 ListView,用项目填充它,并使用挂钩来捕获 ListView 在创建其句柄时发送的 CreateWindow 消息并传回对 win32 的引用ListView 而不是一个新窗口.. 但谁知道会有什么副作用...... Win32 大师需要说出这个疯狂的想法:)

【讨论】:

【参考方案4】:

我使用了这个代码:

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

我还为每一列设置了 GenerateMember 为 false。

链接到自定义列表视图排序器:http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

【讨论】:

是的,在添加项目时激活 sorter 非常慢。但在这种情况下,我没有分拣机。但对于那些可能没有意识到 .NET 列表视图每次添加一个项目而不是在末尾时调用排序的人来说,这是有用的第一步。【参考方案5】:

创建所有 ListViewItems FIRST,然后一次性将它们添加到 ListView

例如:

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

【讨论】:

以上是关于如何加快向 ListView 添加项目?的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中的顶部 ListView 项目上方(以及最后一个项目下方)添加边距

无法将 extra_context 添加到 ListView

如何加快ListView的处置

android -BaseAdapter 不会向 listView 添加新项目

向 Firebase 添加新项目后 ListView 重复 [重复]

如何滚动 ListView 中的项目使其可见?