我正在向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步 - 确保没有列设置为“自动宽度”:
检查
第2步 - 确保ListView在每次添加项目时都没有尝试对项目进行排序:
检查
第3步 - 询问stackoverflow:
检查
注意:显然这个ListView不处于虚拟模式;因为您没有/不能“添加”项目到虚拟列表视图(您设置VirtualListSize
)。幸运的是,我的问题不是虚拟模式下的列表视图。
我是否遗漏了任何可能导致列表视图添加项目如此缓慢的原因?
Bonus Chatter
我知道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包装器的哪些属性?
答案 0 :(得分:21)
我看了一下列表视图的源代码,我注意到一些可能会使性能降低4倍左右的事情,你会看到:
在ListView.cs中,ListViewItemsCollection.AddRange
调用ListViewNativeItemCollection.AddRange
,这是我开始审核的地方
ListViewNativeItemCollection.AddRange
(来自第18120行)有两次遍历整个值集合,一次是收集所有已检查的项目,另一次是在调用InsertItems
后“恢复”它们(它们都是通过对owner.IsHandleCreated
的检查,所有者为ListView
)进行保护,然后拨打BeginUpdate
。
ListView.InsertItems
(来自第12952行),第一次调用,对整个列表进行另一次遍历,然后调用ArrayList.AddRange(可能是另一个传递),然后再进行另一次传递。导致
ListView.InsertItems
(来自第12952行),第二次调用(通过EndUpdate
)另一次传递将它们添加到HashTable
,而Debug.Assert(!listItemsTable.ContainsKey(ItemId))
会减慢它进一步在调试模式下。如果未创建句柄,则会将项目添加到ArrayList
,listItemsArray
但if (IsHandleCreated)
,然后调用
ListView.InsertItemsNative
(来自第3848行)最后通过列表,它实际上已添加到本地列表视图中。 a 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性能的提示等。它似乎表明在创建句柄之前添加项目的速度更快,但是在呈现控件时您将付出代价。也许应用评论中提到的渲染优化并在创建句柄之前添加项目将获得两全其美。
编辑:以各种方式测试这个假设,虽然在创建句柄之前添加项目是快速的,但是在创建句柄时它会指数地慢。我试图欺骗它来创建句柄,然后以某种方式让它调用InsertItemsNative而不经过所有额外的传递,但唉,我被挫败了。我能想到的唯一可能是在c ++项目中创建Win32 ListView,用项填充它,并使用挂钩捕获ListView在创建其句柄时发送的CreateWindow消息并传回对win32的引用ListView而不是一个新的窗口..但谁知道副作用会有什么...一个Win32大师需要说出这个疯狂的想法:)
答案 1 :(得分:8)
我使用了这段代码:
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
答案 2 :(得分:0)
我有同样的问题。然后我发现sorter
让它变得如此之慢。
使分拣机为空
this.listViewAbnormalList.ListViewItemSorter = null;
然后点击排序器时,在ListView_ColumnClick
方法上,将其设为
lv.ListViewItemSorter = new ListViewColumnSorter()
最后,在对其进行排序后,再次使sorter
为空
((System.Windows.Forms.ListView)sender).Sort();
lv.ListViewItemSorter = null;
答案 3 :(得分:0)
这是我可以构造的简单代码,用于将项目添加到由列组成的列表框中。第一列是项目,第二列是价格。 下面的代码在第一栏中打印肉桂色,在第二栏中打印0.50。
// How to add ItemName and Item Price
listItems.Items.Add("Cinnamon").SubItems.Add("0.50");
不需要实例化。
答案 4 :(得分:-2)
创建所有 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 );