Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations wOOdy-Soft on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

A banal idea on how to populate quickly a long (sorted) ListView in C# 1

Status
Not open for further replies.

polocar

Programmer
Sep 20, 2004
89
IT
Hi,
I see bugs everywhere, but this time I think I’m right (probably most of you has already discovered the problem).
I’m writing a C# program using Visual Studio 2005 Professional Edition, and I have prepared a class called ColumnSortedListView, that is a derivated class of ListView, with the possibility to order the ListView object by column.
The code is similar to the one proposed in the MSDN library (if you are interested, you can find my code below): the sorting is customized derivating the IComparer class (as MSDN library does in its example).
I have created a main class, called FormListView (derivated from Form), containing 1 ColumnSortedListView object and 2 buttons: the first called btnPopulateList, and the second called btnClearList (the btnPopulateList adds 1000 ListViewItem objects to the ListView, and the btnClearList clears the ListView, so you can see how much time the program spends to re-populate it).
When the program starts, the ListView is populated for the first time (not sorted respect to anyone of the columns).
If you click on a column header, the ListView is sorted respect to that column. From this moment (as the ListViewItemSorter property is set), if you click on btnClearList and then on btnPopulateList, the ListView is populated sorted respect to the column.
Well, the strange thing is this one:
if you populate the list without sorting (so until you click on a column header for the first time), the list is populated quickly; if you click on a column header, the list is sorted respect to that column quickly. At this point, if you click (first on btnClearList and then) on btnPopulateList, the list is populated (and sorted respect to that column at the same time) very very very slowly.
The obvious solution is: when I have to populate the ListView, before starting I assign the ListViewItemSorter to null (so that the ListView is populated without sorting), and after finishing I call the SortColumn procedure (the same I call when the user clicks on a column header).
In this way the time the program spends to populate the ListView is short.
Isn’t it a bug for you? What do you think about?

Thank you

P.S.: The code is rather long, but I think simple; if anyone wants to try…

As the ColumnSortedListView has 3 columns (the first containing strings, the second integral numbers and the third dates, for precision I have written 3 classes derived from IComparer, that is ListViewItemDatesComparer, ListViewItemNumbersComparer and ListViewItemStringsComparer (in the SortColumn procedure I verify the type of the (first ListViewItem) SubItem relative to the column and assign the ListViewItemSorter to one of the three).

Code:
using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;

class SeparateMain
{
    public static void Main()
    {
        Application.Run(new FormListView());
    }
}

class FormListView : Form
{
    ColumnSortedListView cslvList;
    Button btnClearList, btnPopulateList;

    public FormListView()
    {
        this.Size = new Size(500, 500);
        this.CenterToScreen();

        cslvList = new ColumnSortedListView();
        cslvList.Parent = this;
        cslvList.Size = new Size(390, 400);
        cslvList.Location = new Point(10, 10);
        cslvList.View = View.Details;
        cslvList.MultiSelect = true;
        cslvList.FullRowSelect = true;
        cslvList.HideSelection = false;
        cslvList.Columns.Add("String", 120, HorizontalAlignment.Left);
        cslvList.Columns.Add("Number", 120, HorizontalAlignment.Left);
        cslvList.Columns.Add("Date", 120, HorizontalAlignment.Left);

        btnClearList = new Button();
        btnClearList.Parent = this;
        btnClearList.Size = new Size(90, 20);
        btnClearList.Location = new Point(40, 430);
        btnClearList.Text = "Clear list";
        btnClearList.Click += new EventHandler(btnClearList_Click);

        btnPopulateList = new Button();
        btnPopulateList.Parent = this;
        btnPopulateList.Size = new Size(90, 20);
        btnPopulateList.Location = new Point(150, 430);
        btnPopulateList.Text = "Populate list";
        btnPopulateList.Click += new EventHandler(btnPopulateList_Click);

        PopulateList();
    }

    void btnClearList_Click(object sender, EventArgs e)
    {
        cslvList.Items.Clear();
    }

    void btnPopulateList_Click(object sender, EventArgs e)
    {
        PopulateList();
    }

    void PopulateList()
    {
        if (cslvList.Items.Count > 0)
            MessageBox.Show("Clear the list before populating it", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        else
        {
            cslvList.LooseSorting();

            cslvList.BeginUpdate();

            for (int i = 0; i <= 1000; i++)
            {
                ListViewItem lvi = new ListViewItem("String" + i);
                lvi.SubItems.Add(Convert.ToString(i));
                lvi.SubItems.Add(Convert.ToString(new DateTime(2000, 1, 1) + new TimeSpan(i, 0, 0, 0)));

                cslvList.Items.Add(lvi);
            }

            cslvList.EndUpdate();

            if (cslvList.LastColSorted != -1)
            cslvList.SortColumn(cslvList.LastColSorted, cslvList.LastColSortedOrder);
        }
    }
}

class ColumnSortedListView : ListView 
{
	public enum Order
	{
		Ascending,
		Descending
	}

	private int lastColSorted;
	private ColumnSortedListView.Order lastColSortedOrder;

	public ColumnSortedListView()
	{
		lastColSorted = -1;
		lastColSortedOrder = ColumnSortedListView.Order.Ascending;
	}

    public int LastColSorted
    {
        get
        {
            return lastColSorted;
        }
    }

    public Order LastColSortedOrder
    {
        get
        {
            return lastColSortedOrder;
        }
    }

	protected override void OnColumnClick(ColumnClickEventArgs e)
	{
		base.OnColumnClick (e);
		
        if (lastColSorted != e.Column)
            SortColumn(e.Column, ColumnSortedListView.Order.Ascending);
        else
        {
            if (lastColSortedOrder == ColumnSortedListView.Order.Ascending)
                SortColumn(e.Column, ColumnSortedListView.Order.Descending);
            else   // if (lastColSorted == ColumnSortedListView.Order.Descending)
                SortColumn(e.Column, ColumnSortedListView.Order.Ascending);
        }
	}

	public void SortColumn(int col, ColumnSortedListView.Order or)
	{
        if (Items.Count > 0)
        {
            ListViewItem lvi = Items[0];
            String str = lvi.SubItems[col].Text;

            try
            {
                DateTime dt = Convert.ToDateTime(str);
                ListViewItemSorter = new ListViewItemDatesComparer(col, or);
                Sort();
            }
            catch (Exception exNotDates)
            {
                try
                {
                    int n = Convert.ToInt32(str);
                    ListViewItemSorter = new ListViewItemNumbersComparer(col, or);
                    Sort();
                }
                catch
                {
                    ListViewItemSorter = new ListViewItemStringsComparer(col, or);
                    Sort();
                }
            }
        }

        lastColSorted = col;
        lastColSortedOrder = or;
	}

    public void LooseSorting()
    {
        ListViewItemSorter = null;
    }
}

class ListViewItemDatesComparer : IComparer
{
	private int column;
	private ColumnSortedListView.Order order;

	public ListViewItemDatesComparer(int col, ColumnSortedListView.Order or)
	{
		column = col;
		order = or;
	}

    public int Compare(object x, object y)
    {
        ListViewItem lvx = (ListViewItem)x;
        ListViewItem lvy = (ListViewItem)y;

        String strx = lvx.SubItems[column].Text;
        String stry = lvy.SubItems[column].Text;

        DateTime dx = Convert.ToDateTime(strx);
        DateTime dy = Convert.ToDateTime(stry);

        if (order == ColumnSortedListView.Order.Ascending)
        {
            if (dx < dy)
                return -1;
            else if (dx == dy)
                return 0;
            else
                return 1;
        }
        else
        {
            if (dy < dx)
                return -1;
            else if (dy == dx)
                return 0;
            else
                return 1;
        }
    }
}

class ListViewItemNumbersComparer : IComparer
{
    private int column;
    private ColumnSortedListView.Order order;

    public ListViewItemNumbersComparer(int col, ColumnSortedListView.Order or)
    {
        column = col;
        order = or;
    }

    public int Compare(object x, object y)
    {
        ListViewItem lvx = (ListViewItem)x;
        ListViewItem lvy = (ListViewItem)y;

        String strx = lvx.SubItems[column].Text;
        String stry = lvy.SubItems[column].Text;

        int ix = Convert.ToInt32(strx);
        int iy = Convert.ToInt32(stry);

        if (order == ColumnSortedListView.Order.Ascending)
        {
            if (ix < iy)
                return -1;
            else if (ix == iy)
                return 0;
            else
                return 1;
        }
        else
        {
            if (iy < ix)
                return -1;
            else if (iy == ix)
                return 0;
            else
                return 1;
        }
    }
}

class ListViewItemStringsComparer : IComparer
{
    private int column;
    private ColumnSortedListView.Order order;

    public ListViewItemStringsComparer(int col, ColumnSortedListView.Order or)
    {
        column = col;
        order = or;
    }

    public int Compare(object x, object y)
    {
        ListViewItem lvx = (ListViewItem)x;
        ListViewItem lvy = (ListViewItem)y;

        String strx = lvx.SubItems[column].Text;
        String stry = lvy.SubItems[column].Text;

        if (order == ColumnSortedListView.Order.Ascending)
            return String.Compare(lvx.SubItems[column].Text, lvy.SubItems[column].Text);
        else
            return String.Compare(lvy.SubItems[column].Text, lvx.SubItems[column].Text);
    }
}
 
Did you try wrapping your populate method with calls to SuspendLayout and ResumeLayout?
Code:
try
{
   MyListbox.SuspendLayout();
   PopulateListbox();
}
finally
{
   MyListbox.ResumeLayout();
}
This works faster because when adding rows normally, the listbox will attempt to repaint the screen between each insert. With these calls, it will suspend the paint operation until you tell it it's OK to paint once again.

Chip H.


____________________________________________________________________
If you want to get the best response to a question, please read FAQ222-2244 first
 
If you want to populate a list control with numerous items simultaneously, I usually like to use the addrange method. This serves as a bulk insert operation, instead of inserting items one at a time. I'll add items to a collection then add the collection in the addrange. I've noticed it works significantly faster.

 
Hi guys, thanks for your answers.

Chip, I have tried to add your two methods as you suggested me:

Code:
cslvList.SuspendLayout();
PopulateList();
cslvList.ResumeLayout();

I have put my two statements
Code:
cslvList.LooseSorting();
and
Code:
cslvList.SortColumn(cslvList.LastColSorted, cslvList.LastColSortedOrder);
under comment; unfortunately the populating process isn't faster...

IT4EVR, I have tried your suggestion too (here is how I have modified my populating procedure code):

Code:
void PopulateList()
{
    if (cslvList.Items.Count > 0)
        MessageBox.Show("Clear the list before populating it", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    else
    {
        cslvList.BeginUpdate();

        ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(cslvList);

        for (int i = 0; i <= 1000; i++)
        {
            ListViewItem lvi = new ListViewItem("String" + i);
            lvi.SubItems.Add(Convert.ToString(i));
            lvi.SubItems.Add(Convert.ToString(new DateTime(2000, 1, 1) + new TimeSpan(i, 0, 0, 0)));

            lvic.Add(lvi);
        }

        cslvList.EndUpdate();
    }
}

Also in this case I have put my two statements (LoosSorting and SortColumn) under comment.
Unfortunately, neither in this case the populating procedure becomes faster...)
 
You may not notice a significant difference in the apparent sorting of the listview. The sort is actually very quick when using IComparers - it is usually the drawing that takes forever. I know listviews are quite slow in the drawing process.

Chip's suggestion of the SuspendLayout and ResumeLayout will help speed up this process but you may not notice the difference unless your listview is huge. The only other thing I can think of is turning off visual styles for that control if you are using WinXP : but I don't know what extra drawing is done on the ListView control or ListViewItem in xp.

The next best thing to do would be to handle your own drawing using a custom listview and custom listviewitems.
 
At the end of the day, you have to ask yourself whether putting huge numbers of rows into a listview or combo is advisable because the user can only see so many items anyway.

If your list starts to become long you might want to implement some sort of paging mechanism or filter for records based on the keystrokes.
 
I also would add that BeginUpdate and EndUpdate affect the performance of combo boxes as well. You would call the BeginUpdate before you add items and EndUpdate after you are finished adding items. It prevents repainting of the screen per insertion.

Whether that is better or faster than SuspendLayout, ResumeLayout I don't know. This link claims BeginUpdate provides better performance and that SuspendLayout doesn't provide a benefit...

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top