Alois Kraus

blog

  Home  |   Contact  |   Syndication    |   Login
  133 Posts | 8 Stories | 368 Comments | 162 Trackbacks

News



Archives

Post Categories

Image Galleries

Programming

Did you ever ask yourself during coding how far you can get with simple constructs and when it is time to use the more advanced but also more complex constructs? When you take the next step it can happen that you discover that the more “complex” solution is even more simple! I had such an aha moment with the Windows Forms ListView. I had one in detailed mode with several thousand rows which did show up very slowly because of the huge number of ListViewItem objects inside it.

image

Nothing fancy but for 20K rows it did take about 5s until something was displayed. The code to display items in list view is straightforward:

        class AccountInfo

    {
        public string AccountType
        {
            get;
            set;
        }
 
        public int AccountBalance
        {
            get;
            set;
        }
 
        public int BuyRating
        {
            get;
            set;
        }
    }

    public partial class cAccounting : Form
    {
        List<AccountInfo> myInfos = new List<AccountInfo>();
        public cAccounting()
        {
            InitializeComponent();
            for (int i = 0; i < 20000; i++)
            {
                myInfos.Add(new AccountInfo
                {
                    AccountType = i % 3 == 0 ? "Good" : "Bad",
                    AccountBalance = i,
                    BuyRating = i*2
                });
            }
 
            ShowItems(myInfos);
        }
 
        void ShowItems(List<AccountInfo> infos)
        {
            cList.SuspendLayout();
            foreach (var acc in infos)
            {
                cList.Items.Add( new ListViewItem(new string [] { acc.AccountType, acc.AccountBalance.ToString(), acc.BuyRating.ToString() }));
            }
            cList.ResumeLayout();
        }

So far so good. Now I wanted to make it faster. Let´s see how things do change when we make the ListView virtual. This little change does cause the list view to fetch only the visible items. Only visible ListViewItems are fetched on demand. If you have 10 million rows but display only 100 of them then only 100 ListViewItem objects will be used.

image

We no longer have the Items collection which would throw exceptions when we would try to use it. The SelectedItems collection is also gone. Instead the SelectedIndices is your friend. Well nearly I found that my items were deselected without beeing notified by the ItemSelectionChanged event so I stored the last selected item index by myself when the SelectedIndicies collection was not empty.

But back to our virtual list view: We only need to register to the RetrieveVirtualItem event and set the VirtualListSize property to the number of items our ListView will contain. That´s all.

image

The contract is that the event tells you for which ItemIndex in the list view wants to draw a ListViewItem and you set the Item property correctly.

 
        private void ShowItemsVirtual(List<AccountInfo> infos)
        {            
            cList.VirtualListSize = infos.Count; // Set number of items in list view
        }
 
        private void cList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            var acc = myInfos[e.ItemIndex];
            // hand out new item
            e.Item = new ListViewItem(
                new string [] 
                { acc.AccountType, acc.AccountBalance.ToString(), acc.BuyRating.ToString() })
                { Tag = acc }; // Set Tag object property to our actual AccountInfo object
        }

 

That was not hard. And we get a display speed increase of over a factor 5 with little effort. The startup time has dropped from 5s to below 1s. But the best thing is that we no longer have set the state of our ListViewItems when we want to e.g. color them. Previously we needed to traverse the whole Items collection and set the e.g. BackColor property for the required ListViewItems by hand. Now we can store the state in one variable and every time the list view is redrawn we hand out ListViewItem objects with the updated state. Bye bye list traversing and setting properties.

To update the color of every second row we need to insert only one additonal line in our RetrieveVirtualItem callback:

 

image

 

        private void cList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            var acc = myInfos[e.ItemIndex];
            e.Item = new ListViewItem(
                new string [] 
                { acc.AccountType, acc.AccountBalance.ToString(), acc.BuyRating.ToString() })
                { Tag = acc,
                  BackColor = e.ItemIndex % 2 == 0 ? Color.Aqua : Color.White
                }; 
        }

To display the colored items in the current view we need to call Refresh to force the list view to fetch our new ListViewItems. We have full control which item is on which position rendered. Now it is time for sorting by different columns. In virtual mode the usual ListViewItem sorter is not supported so we have to roll our own. I find the code below actually easier to write because I do know that the data is stored in List<AccountInfo> myInfos and we only need to sort the list by the current sort column ascending or descending order.

       // hold state for each colum which sort order should be used
        Dictionary<int,SortOrder> mySortOrderMap = new Dictionary<int,SortOrder>
        {
            {0, SortOrder.None },
            {1, SortOrder.None },
            {2, SortOrder.None }
        };
 
        // define comparer for each column
        Dictionary<int, Comparison<AccountInfo>> myComparers = new Dictionary<int, Comparison<AccountInfo>>
        {
            {0, (a,b) => a.AccountType.CompareTo(b.AccountType)},
            {1, (a,b) => a.AccountBalance.CompareTo(b.AccountBalance)},
            {2, (a,b) => a.BuyRating.CompareTo(b.BuyRating)}
        };
 
        // State transitions from one sort order to another
        Dictionary<SortOrder, SortOrder> myToggle = new Dictionary<SortOrder, SortOrder>
        {
            {SortOrder.None, SortOrder.Ascending },
            {SortOrder.Ascending, SortOrder.Descending},
            {SortOrder.Descending, SortOrder.Ascending}
        };
 
        private void cList_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            var newSortOrder = myToggle[mySortOrderMap[e.Column]];
            mySortOrderMap[e.Column] = newSortOrder;     // Store sort order for current column
            SortBy(newSortOrder, myComparers[e.Column]); // Do the actual sorting
        }
 
        void SortBy(SortOrder order, Comparison<AccountInfo> comparer)
        {
            myInfos.Sort((a, b) =>
                {
                    int lret = comparer(a, b); // Do the actual comparison
                    if (order == SortOrder.Descending) // reverse when necessary
                    {
                        lret *= -1;
                    }
                    return lret;
                });
 
            cList.Refresh();
        }

This was much easier than I thought and what I especially like about virtual list views is that I do not have to worry about the state of our rendered ListViewItems. Instead the “factory” event will provide items with the newest state directly to the view once we invalidated the list view by calling Refresh.

posted on Monday, June 21, 2010 9:56 AM

Feedback

# re: Slow ListView? – Virtual ListView! 9/1/2010 10:48 PM Bimbim.in
For sorting List View Item while list view in virtual mode is very easy. Just depending upon clicked column sort you data it will be displayed sorted.

Here i am giving example with other approch when my ListViewItem list is already created. Please refer following link

http://bimbim.in/post/2010/09/02/Listview-in-virtual-Mode-sorting-in-C-Sharp.aspx

# re: Slow ListView? – Virtual ListView! 9/2/2010 12:05 AM Alois Kraus
Hi Bimbim,

your approach does work if you create all ListViewItems in advance. This does work but costs you a slow startup since you need to create all 50K ListViewItems before anything is displayed. If you would dynamically create the ListViewItems it would be much faster at startup but then your sort approach does no longer work since you are sorting ListViewItems and not the original data list from where the ListViewItems would be created.
Besides that my approach is shorter and does not have the limitations mentioned above.

Yours,
Alois Kraus



# re: Slow ListView? – Virtual ListView! 12/9/2010 12:48 AM Alex
This article shows how you can load huge numbers of items into the listview very quickly.

http://www.bytechaser.com/en/articles/f3a3niqyb7/display-large-lists-in-listview-control-quickly.aspx

# re: Slow ListView? – Virtual ListView! 11/18/2011 9:59 AM Richard Moss
Thanks for the post, I found this very useful for a ListView I recently converted to a virtual one and which was in dire need of sorting!

Regards;
Richard Moss

# re: Slow ListView? – Virtual ListView! 8/5/2015 8:59 AM goroph
this made my program run better, but a few things broke using this method
say my listView is listView1 then
1.) listView1.SelectedItems throws an exeption

listView1.Activation is TwoClick and the activation method is
listView1_ItemActivate(sender, e)

it will call it twice instead of once, and every click thereafter so long as the mouse is on the same line

Post A Comment
Title:
Name:
Email:
Comment:
Verification: