listview虚拟列表,e.ItemIndex崩溃!
问题可能很简单,帖子比我希望的要长,但我已尝试提供尽可能多的信息和细节。 这个 GUI 应用程序不是我编写的,也不是我设计的,但是像我们大多数人一样,我继承了它。
它有一个(常规)ListView,实际上应用程序有几个 ListView,不确定这是否重要。 由于到达此 ListView(屏幕/表单)的项目数量可能会超过 10K,因此我决定将其转换为虚拟列表,但是我遇到了一些早期问题。
最大的问题之一是通过点击表单上的按钮来异步填充项目。 当它们到达时(从服务/网络/数据库),这些项目被构建到 ListViewItem(s) 中并添加到 someListItems(这是一个 ArrayList)中。
在我的 RetrieveVirtualItem 方法中,我需要处理列表为空时和我已经有东西时(按下按钮后)以及当我碰壁时的两种情况(没有双关语意图) 使用以下代码行:
if ( someListItems.Count > e.ItemIndex )
它基本上会导致(不知道为什么)调用主窗体上的 Dispose 方法,从而导致整个应用程序严重崩溃。但是!!,只有当我单击表单和列表时才会发生。如果表单刚刚加载并填充,那就没问题了……当您单击鼠标左键时,轰隆隆!
我花了几个小时才发现上面的行是罪魁祸首,因为调用堆栈不是很明显地指出了这一点,又花了一分钟才发现 e.ItemIndex
是罪魁祸首。但为什么???我 在 msdn 示例中,他们访问 e.ItemIndex 来执行测试,看起来不错。
虚拟模式在表单的构造函数中设置:
myListView.VirtualMode = true;
VirtualListSize 在数据异步到达后立即设置:
myListView.VirtualListSize = someArrayList.Count;
这是我的 RetrieveVirtualItem 实现:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// someListItems is an ArrayList that is created when the object/class loads..and populated with ListViewItems.
// i.e. private ArrayList someListItems = new ArrayList();
// it is populated asynchronously by hitting a button on the form, hence it's empty when the form loads..
if ( someListItems.Count <= 0 )
{
e.Item = new ListViewItem( "" );
e.Item.SubItems.Add( "" );
e.Item.SubItems.Add( "" );
}
else
{
// the of code below is the problem, and more specifically - e.ItemIndex causes somehow to call Dispose on the main form..
// the reason I have this code is because if I take it out, all items will show up, no problem, but it will crash when I try to scroll down..
// with message like this:
// Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
if ( someListItems.Count > e.ItemIndex )
{
// took out my code out to eliminate possibility that it's my code. :)
int x = e.ItemIndex * e.ItemIndex;
e.Item = new ListViewItem( x.ToString() );
// but I had something like that just for a test:
// ListViewItem item = ( ListViewItem )someListItems[e.ItemIndex];
// e.Item = item;
// remember that someListItems already has ListViewItems
}
}
}
异步调用的方法,创建 ListViewItems 并填充 someListItems 看起来像这样:
private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
{
//Im only showing more essential code..
SomeArrayList.Items.Clear();
myListView.VirtualListSize = ar.Count;
foreach ( SomeObject o in ar )
{
ListViewItem lvi = new ListViewItem( SomeObject.somePropertyID, 0 );
// I've tried changing the above line to: lvi = new ListViewItem( SomeObject.somePropertyID, 0 ); // and having the ListViewItem lvi on the class level. i.e private ListViewItem lvi
// didn't help.. :(
lvi.SubItems.Add( o.someProperty1 );
lvi.SubItems.Add( o.someProperty2 );
// there's quite few of these subitems..2 is enough for this example...
}
// the orignal code, before I changed it to virtual list was adding the items somewhere here..after finished looping, now I'm just trying to reuse that array of ListViewItems.
}
还有另一个问题,除非我取出:,否则项目根本不会真正显示出来:
if ( someListItems.Count > e.ItemIndex )
但是当我尝试滚动时,我遇到了索引超出范围的问题。
更新:
我注意到,如果我设置虚拟列表的大小,则仅在循环完成后才设置,因此它在开始时为零(0)(我总是可以将其重置为零),然后一切正常,不需要检查大小,我所要做的就是:
在循环之后: private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
this.myListView.VirtualListSize = someListItems.Count;
我要感谢 汉斯·帕桑 (Hans Passant) 注意到了这种差异。 所以现在这就是完整的(我确信我会添加一些代码或更改,因为我想添加一些缓存,但至少我有一些东西......
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
e.Item = ( ListViewItem )someListItems[e.ItemIndex];
}
我唯一不确定的是什么 Hans Passant 提到的是:“这个事件处理程序从不分配 ListViewItem 确实是不行的。” 我不确定我是否理解,因为 ListViewItems被分配并插入到 someListItems 数组中,我确实有一个 try catch ,我之前也做过
此外,我在想,我会感谢有人对这个想法的意见: 创建一个单独的对象来保存 SomeObject 的所有属性,或者将 SomeObject 插入列表中并根据需要创建新的 ListViewItems? 例如:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// that list would be build sometime during the loop iteration in
// (I'm using the original method name mentioned way above in this post)
// ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
SomeObject o = listOfObjects[e.ItemIndex];
e.Item = new ListViewItem();
e.Item.SubItems.Add(o.prop1);
e.Item.SubItems.Add(o.prop2);
e.Item.SubItems.Add(o.prop3);
}
the problem is probably simple, the post is longer than I wished, but I've tried providing as much info and detail as possible.
I didn't write this GUI app, nor designed, however like most of us I've inherited it.
It had a (regular) ListView, actually the app has several ListView(s), not sure if that matters yet.
Because the # of items arriving to this one ListView (screen/form) can get very large 10K+ I decided to convert it to virtual list, however I'm experiencing some early problems.
One of the biggest problems, is that the items are being populated asynchronously by hitting a button on the form.
When they arrive (from service/network/database) the items are built into ListViewItem(s) and added to someListItems which is an ArrayList.
In my RetrieveVirtualItem method I need to handle both cases when the list is empty and when I already have something (after the button was hit) and that's when I hit the wall (no pun intended)
with the following line of code:
if ( someListItems.Count > e.ItemIndex )
It basically causes (no idea why) a call to Dispose method on the main form which results in the entire application crashing hard. BUT!!, it only happens when I click on the form and list. If the form is just loaded and populated it is fine..the second you left click on the mouse, BOOM!
It took my couple of hours to figure out that the line above was the culprit, as the call stack wasn't very apparent to point that out, and another minute to find out that e.ItemIndex
is the culprit. But WHY??? I
n msdn examples they access e.ItemIndex to perform tests and it seems fine.
The Virtual Mode is set in the constructor of the form:
myListView.VirtualMode = true;
VirtualListSize is set right after data arrives asynchronously:
myListView.VirtualListSize = someArrayList.Count;
This is my RetrieveVirtualItem implementation:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// someListItems is an ArrayList that is created when the object/class loads..and populated with ListViewItems.
// i.e. private ArrayList someListItems = new ArrayList();
// it is populated asynchronously by hitting a button on the form, hence it's empty when the form loads..
if ( someListItems.Count <= 0 )
{
e.Item = new ListViewItem( "" );
e.Item.SubItems.Add( "" );
e.Item.SubItems.Add( "" );
}
else
{
// the of code below is the problem, and more specifically - e.ItemIndex causes somehow to call Dispose on the main form..
// the reason I have this code is because if I take it out, all items will show up, no problem, but it will crash when I try to scroll down..
// with message like this:
// Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
if ( someListItems.Count > e.ItemIndex )
{
// took out my code out to eliminate possibility that it's my code. :)
int x = e.ItemIndex * e.ItemIndex;
e.Item = new ListViewItem( x.ToString() );
// but I had something like that just for a test:
// ListViewItem item = ( ListViewItem )someListItems[e.ItemIndex];
// e.Item = item;
// remember that someListItems already has ListViewItems
}
}
}
The method that gets called asynchronously, creates ListViewItems and populates someListItems looks something like that:
private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
{
//Im only showing more essential code..
SomeArrayList.Items.Clear();
myListView.VirtualListSize = ar.Count;
foreach ( SomeObject o in ar )
{
ListViewItem lvi = new ListViewItem( SomeObject.somePropertyID, 0 );
// I've tried changing the above line to: lvi = new ListViewItem( SomeObject.somePropertyID, 0 ); // and having the ListViewItem lvi on the class level. i.e private ListViewItem lvi
// didn't help.. :(
lvi.SubItems.Add( o.someProperty1 );
lvi.SubItems.Add( o.someProperty2 );
// there's quite few of these subitems..2 is enough for this example...
}
// the orignal code, before I changed it to virtual list was adding the items somewhere here..after finished looping, now I'm just trying to reuse that array of ListViewItems.
}
There's also another problem that the items don't really show up at all unless I take out the:
if ( someListItems.Count > e.ItemIndex )
but then I experience the index of out of range issue when I try scrolling.
UPDATE:
I've noticed that if I set the size of virtual list, only after the loop is finished and therefore it is zero (0) at the beginning (I can always reset it to zero), then everything works and don't need to check for size, all I have to do is this:
After the loop in: private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
this.myListView.VirtualListSize = someListItems.Count;
which I would like to thank for Hans Passant for noticing the discrepancy.
So this is the complete, for now (I'm sure that I'll add some code or change as I would like to add some caching, but at least I have something...
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
e.Item = ( ListViewItem )someListItems[e.ItemIndex];
}
The only thing I'm not sure what Hans Passant mentioned is this: "it really isn't okay for this event handler to never allocate a ListViewItem. " which I'm not sure if I understand, because the ListViewItems are allocated and inserted into someListItems array. I do have a try catch around, and I did before as well.
Also, I was thinking and I would appreciate someone's input on this idea:
Create a separate object that would hold all the properies of SomeObject or insert SomeObject(s) into the List and create new ListViewItems as required?
e.g:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// that list would be build sometime during the loop iteration in
// (I'm using the original method name mentioned way above in this post)
// ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
SomeObject o = listOfObjects[e.ItemIndex];
e.Item = new ListViewItem();
e.Item.SubItems.Add(o.prop1);
e.Item.SubItems.Add(o.prop2);
e.Item.SubItems.Add(o.prop3);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
来回答这个问题。由于 VirtualListSize 设置不正确,虚拟列表崩溃了。
基本上,为了帮助其他人,如果您有虚拟列表,请始终确保 VirtualListSize 对应于您尝试显示的实际项目数。如果没有,一切都会崩溃。如果您确实更新、删除、添加任何内容,则需要将 VirtualListSize 重置为正确的数字。
我最终从 ListView 派生并将我的 listviewitems 存储在数组中。
To answer this question. The virtual list was crashing because the VirtualListSize wasn't being set correctly.
Basically, to help others here, if you have a virtual list, always make sure that the VirtualListSize corresponds to the actual number of items you're trying to show. If not, all hell breaks loose. If you do update, remove, add, anything, you need to reset VirtualListSize to the correct number.
I ended up deriving from ListView and storing my listviewitems in an array.