| |
After spending a few frustrated hours on multiple column sorting for ListViews because I couldn’t find a single good tutorial, I decided to write my own tutorial talking about how ListViews work and how to sort multiple columns. It’s 11:48 pm, so let’s get cracking before I get too sleepy.
I am using Visual Studio.NET to do the programming and will try to adapt pure code instead of using the pre-generated code done by Visual Studio. I assume that you have already generated a basic C# Windows Application project so I will just write new functions and variables that will create the ListView.
In this tutorial we are going to create a ListView and have 3 columns for First Name, Last Name, Age. We will have functions to add and sort the columns.
We start off by declaring our basic variables.
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.ColumnHeader columnFirstName;
private System.Windows.Forms.ColumnHeader columnLastName;
private System.Windows.Forms.ColumnHeader columnAge;
|
listView1 is going to be our main ListView and we are declaring the variables for the columns. We then move on to initializing the variables.
private void InitListView()
{
listView1 = new ListView();
columnFirstName = new ColumnHeader();
columnLastName = new ColumnHeader();
columnAge = new ColumnHeader();
listView1.Bounds = new Rectangle(new Point(10,10), new Size(305,200));
listView1.View = View.Details;
listView1.FullRowSelect = true;
listView1.Sorting = SortOrder.Ascending;
listView1.GridLines = true;
columnFirstName.Text = "First Name";
columnFirstName.Width = 100;
columnLastName.Text = "Last Name";
columnLastName.Width = 100;
columnAge.Text = "Age";
columnAge.Width = 100;
listView1.Columns.AddRange(new ColumnHeader[] { columnFirstName, columnLastName, columnAge });
this.Controls.Add(listView1);}
|
The code above is fairly simple to understand. We first new up our variables and then we set the size and position of the ListView. The ListView has a few modes of viewing. Take a look at Windows Explorer and you can view the contents as small icons, list, details, etc. We are going to look at viewing things in Details mode. We also are going to set the full select to true, which means that when you select a row, the whole row will become highlighted. We will discuss about the sorting later. After filling out the list view information we fill out our column information by setting the Text and the width. We then add it to our ListView and add our ListView to the controls of the form.
We now move on to adding data to our simple ListView.
private void AddData(string fName, string lName, int Age)
{
ListViewItem lvi;
string[] aHeaders = new string[3];
aHeaders[0] = fName;
aHeaders[1] = lName;
aHeaders[2] = Age.ToString();
lvi = new ListViewItem(aHeaders);
listView1.Items.Add(lvi);
}
|
In this function we are accepting 3 parameters for the information to be filled out. We then create an array of strings of size 3 because one of the constructors of a ListViewItem takes a string[]. We fill it out accordingly and add it to the ListView. So far so good, so now we are going to be doing the fun stuff – sorting.
To start off on sorting I am going to explain a few parameters about the ListView class. In the ListView class it has a property called ListViewItemSorter which basically takes in an IComparer object and uses it to do all the comparisons. The good thing about this comparer object is that now you can monitor the columns being clicked and use your own sort methods on the ListView. So let’s go ahead and create this comparer object.
public class Sorter : IComparer
{
public int column = 0;
public bool bAscending = true;
public int Compare(object x, object y)
{
ListViewItem lvi1 = (ListViewItem) x;
ListViewItem lvi2 = (ListViewItem) y;
if(column != 2)
{
string lvi1String = lvi1.SubItems[column].ToString();
string lvi2String = lvi2.SubItems[column].ToString();
if(bAscending)
return String.Compare(lvi1String, lvi2String);
return -String.Compare(lvi1String, lvi2String);
}
int lvi1Int = ParseListItemString(lvi1.SubItems[column].ToString());
int lvi2Int = ParseListItemString(lvi2.SubItems[column].ToString());
if(bAscending)
{
if(lvi1Int < lvi2Int)
return -1;
else if(lvi1Int == lvi2Int)
return 0;
return 1;
}
if(lvi1Int > lvi2Int)
return -1;
else if(lvi1Int == lvi2Int)
return 0;
return 1;
}
private int ParseListItemString(string x)
{
int counter = 0;
for(int i = x.Length - 1; i >= 0; i--, counter++)
{
if(x[i] == '{')
break;
}
return Int32.Parse(x.Substring(x.Length - counter, counter-1));
}
}
|
Wow, we’re almost done. So now we have created this Sorter object to pass into our ListView. With this object we can now check the column being clicked by setting the column of the class. Looking at the code you can see that we grab the actual item from the SubItems of the specified column and do our comparison on that. It will return a normal comparison of Object X < Object Y which is what the Compare function does. We also check to see if we want to do Ascending or Descending values because when you click a column, if it’s sorted Ascending, we now want it sorted Descending.
There is something funny about the code if you have noticed. I had to make a ParseListItemString function to take in a ListViewItem string and change it to an int. The thing about ListViewItems is they return a string like this: "ListViewSubItem: {19}" so you have to get the int from that string. I did a simple routine to find the ‘{‘ and get the integer.
So now lets make it sort when the column headers are clicked. In Windows Forms, when a column header is clicked it sends off a ColumnClickEventHandler. So now let’s bind the ColumnClickEventHandler to a function. To do this we are going to add the following line of code to our InitListView function.
listView1.ColumnClick += new ColumnClickEventHandler(listView1_ColumnClick);
|
We are then going to create the corresponding function that was added to this delegate.
private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
Sorter columnSorter = new Sorter();
columnSorter.column = e.Column;
if((columnSorter.bAscending = (listView1.Sorting == SortOrder.Ascending)))
listView1.Sorting = SortOrder.Descending;
else
listView1.Sorting = SortOrder.Ascending;
listView1.ListViewItemSorter = columnSorter;
}
|
Yay! So now we are all done. Let me finish up by explaining the click event. The way the tutorial does this is very simple. The ListView has a property called Sorting which takes in a SortOrder. Normally what this property does is that it allows you to choose if the items are going to be sorted Ascending or Descending for the Default sorter. But since we made our own IComparer function, the Sorting property does absolutely nothing. I decided to use the property to keep track of which way I last sorted. I could have also made a global Boolean to do this but I saved a variable and used the ListView’s property. So when I click on a column header, I check if my ListView is currently in Ascending sort. If so then put it to Descending sort.
Next, the ColumnClickEventArgs will give me the column that is being clicked. This is a wonderful tool because now I can pass in the column into my Sorter class and now that class knows what column to sort by. I then pass the Sorter class to the ListViewItemSorter property, and voila! I am done.
I made a very simple application that has a button that will create the ListView and add data to it. Here is the complete code:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Collections;
using System.Drawing;
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
this.button1.Location = new System.Drawing.Point(440, 232);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 32);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(528, 266);
this.Controls.AddRange(new System.Windows.Forms.Control[] {this.button1});
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
static void Main()
{
Application.Run(new Form1());
}
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.ColumnHeader columnFirstName;
private System.Windows.Forms.ColumnHeader columnLastName;
private System.Windows.Forms.ColumnHeader columnAge;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.ColumnHeader columnWeight;
private void InitListView()
{
listView1 = new ListView();
columnFirstName = new ColumnHeader();
columnLastName = new ColumnHeader();
columnAge = new ColumnHeader();
listView1.Bounds = new Rectangle(new Point(10,10), new Size(305,200));
listView1.View = View.Details;
listView1.FullRowSelect = true;
listView1.Sorting = SortOrder.Ascending;
listView1.GridLines = true;
columnFirstName.Text = "First Name";
columnFirstName.Width = 100;
columnLastName.Text = "Last Name";
columnLastName.Width = 100;
columnAge.Text = "Age";
columnAge.Width = 100;
listView1.Columns.AddRange(new ColumnHeader[] { columnFirstName, columnLastName, columnAge });
this.Controls.Add(listView1);
listView1.ColumnClick += new ColumnClickEventHandler(listView1_ColumnClick);
}
private void AddData(string fName, string lName, int Age)
{
ListViewItem lvi;
string[] aHeaders = new string[3];
aHeaders[0] = fName;
aHeaders[1] = lName;
aHeaders[2] = Age.ToString();
lvi = new ListViewItem(aHeaders);
listView1.Items.Add(lvi);
}
public class Sorter : IComparer
{
public int column = 0;
public bool bAscending = true;
public int Compare(object x, object y)
{
ListViewItem lvi1 = (ListViewItem) x;
ListViewItem lvi2 = (ListViewItem) y;
if(column != 2)
{
string lvi1String = lvi1.SubItems[column].ToString();
string lvi2String = lvi2.SubItems[column].ToString();
if(bAscending)
return String.Compare(lvi1String, lvi2String);
return -String.Compare(lvi1String, lvi2String);
}
int lvi1Int = ParseListItemString(lvi1.SubItems[column].ToString());
int lvi2Int = ParseListItemString(lvi2.SubItems[column].ToString());
if(bAscending)
{
if(lvi1Int < lvi2Int)
return -1;
else if(lvi1Int == lvi2Int)
return 0;
return 1;
}
if(lvi1Int > lvi2Int)
return -1;
else if(lvi1Int == lvi2Int)
return 0;
return 1;
}
private int ParseListItemString(string x)
{
int counter = 0;
for(int i = x.Length - 1; i >= 0; i--, counter++)
{
if(x[i] == '{')
break;
}
return Int32.Parse(x.Substring(x.Length - counter, counter-1));
}
}
private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
Sorter columnSorter = new Sorter();
columnSorter.column = e.Column;
if((columnSorter.bAscending = (listView1.Sorting == SortOrder.Ascending)))
listView1.Sorting = SortOrder.Descending;
else
listView1.Sorting = SortOrder.Ascending;
listView1.ListViewItemSorter = columnSorter;
}
private void button1_Click(object sender, System.EventArgs e)
{
InitListView();
AddData("Victor", "Vuong", 19);
AddData("Alan", "Gasperini", 20);
AddData("John", "Gallardo", 22);
AddData("Jessica", "Kim", 22);
AddData("Nina", "Flores", 21);
AddData("Baby", "Boy", 4);
AddData("Old", "Fogie", 100);
}
}
|
|
|