views:

44

answers:

1

Hello, 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);         
    } 
A: 

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.

ra170