Windows Form 按列排序 ListView 项目
1、简介
ListView 控件是显示文件系统信息和显示 XML 或数据库数据的非常好的方式。ListView 控件通常用于显示表示项目以及项目文本的图形图标。此外,ListView 控件还可以用于显示有关子项目中项目的其他信息。例如,如果 ListView 控件显示一列文件,您可以配置 ListView 控件来显示作为子项目的诸如文件大小和属性的详细信息。要显示 ListView 控件中的子项目信息,必须将 View 属性设置为 View.Details。此外,您必须创建 ColumnHeader 对象并将这些对象分配给 ListView 控件的 Columns 属性。在设置这些属性后,项目以行和列格式进行显示,类似于 DataGrid 控件。以这种方式显示项目的能力使 ListView 控件为从任意类型的数据源显示数据提供了快速、简便的解决方案。
对 ListView 控件进行排序是通过使用 ListView 的 Sorting 属性而提供的。这使您可以定义要应用到项目的排序类型。如果您只想按项目排序,这是一个非常好的功能。如果您要按子项目排序,必须使用 ListView 控件的自定义排序功能。本文将说明如何在 ListView 控件中执行自定义排序,以及在排序时如何处理特殊的数据类型条件。
2、ListView 控件的自定义排序功能
ListView 控件提供了您可以使用排序的功能,而不是由 Sorting 属性提供。当 ListView 控件使用 Sorting 属性排序项目时,它使用一个实现 System.Collections.IComparer 接口的类。这个类提供用于排序每个项目的排序功能。为了按子项目进行排序,您必须创建自己的类,来实现反过来可以实现 ListView 控件所需排序的 IComparer 接口。该类利用构造函数进行定义,该构造函数可以指定 ListView 控件排序所用的列。在您创建这个类后(通常将其作为窗体的嵌套类),您可以创建该类的一个实例,并将其分配到 ListView 的 ListViewItemSorter 属性。当调用 Sort 方法时,这会确定 ListView 控件将要使用的自定义排序类。Sort 方法执行 ListView 项目的实际排序。
3、创建 ListView 控件的实例
在控件位于窗体上后,使用 Items 属性将项目添加到 ListView 控件。您可以添加任意多的项目,只要确保每个项目的文本都是唯一的。在您创建项目时,为每个项目添加两个子项目。第一个子项目应该包含数字信息,第二个子项目包含日期信息。下面的表格示例说明该信息在 ListView 控件中可能如何显示。
创建两个 ColumnHeader 对象,并将它们分配到 ListView 控件的 Columns 属性中。将 View 属性设置为 View.Details。
4、处理 ColumnClick 事件
为了确定按哪个子项目集进行排序,您需要了解用户何时单击某个子项目的列标题。为此,您需要为 ListView 的 ColumnClick 事件创建一个事件处理方法。将事件处理方法作为窗体中的一个成员,并确保它包含的签名相似于下面代码示例所显示的签名。
private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
}
通过向窗体的构造函数中添加代码,将事件处理方法连接到 ListView 控件,如下面的示例所示。
this.listView1.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.listView1_ColumnClick);
将下面的代码添加到用于 ColumnClick 事件的事件处理方法。
// 设定一个新的ListViewItemComparer ListViewItemSorter 对象
this.listView1.ListViewItemSorter = new ListViewItemComparer(e.Column);
// 呼出手动排序类
listView1.Sort();
添加到事件处理方法的代码会利用 ListViewItemComparer 类(在下一部分中定义)的新实例来设置 ListView 控件的 ListViewItemSorter 属性,然后分配要单击的列。被单击的列作为事件参数的组成部分进行传递。在设置 ListViewItemSorter 属性后,调用 Sort 方法来执行手动排序。
5、创建 ListViewItemComparer 类
正如前面提到的那样,在 ListView 控件中进行自定义排序的关键在于创建实现 System.Collections.IComparer 接口的类。就是这个类为项目提供排序。对于这个示例,定义了名为 ListViewItemComparer 的类,并且将其添加为窗体的嵌套类。ListViewItemComparer 执行传递到其构造函数的指定列的基本升序排序。将以下类定义添加到 Form 类并确保它在窗体内是正确嵌套的。
// 实行手工对项目的列进行排序.
class ListViewItemComparer : IComparer {
private int col;
public ListViewItemComparer() {
col=0;
}
public ListViewItemComparer(int column)
{
col=column;
}
public int Compare(object x, object y)
{
int returnVal = -1;
returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
((ListViewItem)y).SubItems[col].Text);
return returnVal;
}
}
以一个名为 Compare 的 IComparer 接口的必要方法执行排序。这个方法将两个对象作为参数,而参数会包含要进行比较的两个项目。当在 ListView 控件的 ColumnClick 事件处理方法中调用 Sort 方法时,Sort 方法会使用已定义并已分配到 ListViewItemSorter 属性的 ListViewItemComparer 对象,并且调用其 Compare 方法。创建 ListViewItemComparer 对象后,分配给它被单击的列的索引。该列的索引用于从需要进行排序的列中访问子项目。然后,将子项目传递到 String.Compare 方法,该方法比较项目并返回三个结果中的一个。如果 x 参数中的项目小于 y 参数中的项目,则返回一个小于零的值。如果两个项目相同,则返回零。最后,如果 x 参数中的值大于 y 参数中的值,则返回一个大于零的值。Compare 方法返回的值传递回 Sort 方法,这确定正在比较的每个项目在列中的位置。Sort 方法可以根据在所选择列中排序所有子项目的需要对 Compare 方法调用任意多次。
前面的示例就完成了。如果您运行该示例并单击 ListView 控件的列标题,项目将会按字母顺序或数字顺序进行适当地排序。唯一不能正确排序的列就是包含日期信息的列。
升序或降序排序
ListView 控件的用户将期望具有同时以升序和降序排序项目的功能。为了实现这个功能,需要对前面的示例进行某些改动,以便使 Compare 方法可以确定要排序的项目。
1、对窗体的更改
通常情况下,要在升序和降序排序之间切换,您要多次单击列标题。用户期望如果他们单击列标题,排序将会发生,随后再次单击将更改排序顺序。前面的代码示例需要能够确定何时多次单击列。为此,您可以将一个私有整数变量添加到 Form 类。这个变量存储上一次单击的列。ColumnClick 事件处理方法将使用这个变量来比较上一次的列与当前单击的列,并确定它们是否相同。将以下成员定义添加到 Form 类中。
private int sortColumn = -1;
ColumnClick 事件处理方法的更改
在前面的示例中定义的 ColumnClick 事件处理方法需要进行修改,以便跟踪单击过的列和当前排序顺序。添加以下代码以替换在前面的示例中创建的 ColumnClick 事件处理方法中的代码。
// 实现列的排序
class ListViewItemComparer : IComparer {
private int col;
private SortOrder order;
public ListViewItemComparer() {
col=0;
order = SortOrder.Ascending;
}
public ListViewItemComparer(int column, SortOrder order)
{
col=column;
this.order = order;
}
public int Compare(object x, object y)
{
int returnVal= -1;
returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
((ListViewItem)y).SubItems[col].Text);
// 确定排序顺序是否是降.
if(order == SortOrder.Descending)
//反转的String.Compare返回的值.
returnVal *= -1
return returnVal;
}
}
该代码将排序顺序参数添加到构造函数,并且创建一个用于存储该值的私有变量。Compare 方法的代码已更改,以便确定排序顺序是否为降序。如果是,则将String.Compare 方法的返回值乘以 -1 以更改该值,这样 Compare 方法返回的值就与 String.Compare 返回的值相反。
运行该代码并单击列。该列以升序顺序进行排列。单击相同的列,列就会以降序进行排序。同样,日期列没有正确的排序,因为它被存储为一个字符串而不是日期。在接下来的部分中,通过添加功能来按日期或者按字符串进行排序(这取决于数据的类型),从而完成该示例
2、排序日期
作为项目而置于 ListView 控件中的数据显示为文本并以文本的形式进行存储。这使得使用 IComparer 类中的 String.Compare 方法进行排序变得非常简单。String.Compare 可以对字母字符和数字进行排序。但是,使用 String.Compare 不能对特定数据类型进行正确排序,例如日期和时间信息。因此,System.DateTime 结构具有一个与 String 类相同作用的 Compare 方法。这个方法可以用来基于时间顺序执行相同类型的排序。在本部分中,您只需修改 Compare 方法就可以正确排序日期。
添加以下代码以替换为 Compare 方法(该方法是前面示例中定义的 ListViewItemComparer 类的方法)而定义的代码。
public int Compare(object x, object y)
{
int returnVal;
// 确定是否被比较的类型是日期类型.
try
{
// 解析两个对象作为参数传递作为一个DateTime.
System.DateTime firstDate =
DateTime.Parse(((ListViewItem)x).SubItems[col].Text);
System.DateTime secondDate =
DateTime.Parse(((ListViewItem)y).SubItems[col].Text);
// 比较两个日期.
returnVal = DateTime.Compare(firstDate, secondDate);
}
// 如果既没有比较的对象有一个有效的日期格式,作为一个字符串比较.
catch
{
// 作为一个字符串进行比较的两个项目.
returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
((ListViewItem)y).SubItems[col].Text);
}
// 确定排序顺序是否是降.
if (order == SortOrder.Descending)
// 反转的String.Compare返回的值.
returnVal *= -1;
return returnVal;
}
通过将 x 和 y 参数存放到 DateTime 对象中,启动已添加用于替换 Compare 方法早期版本的代码。通过强制将两个正在比较的对象存放到 DateTime 对象中,在 try/catch 块中执行这个摘录以捕获可能出现的异常。如果出现异常,它发信号通知代码正在转换的类型是无效日期或时间,可以使用 String.Compare 方法进行排序。如果两个类型都是日期,它们使用 DateTime.Compare 方法进行排序。
运行该示例代码的这个新版本,并单击任意列。您将注意到它们正确地排序子项目,包括日期列。现在,该示例中的 ListView 控件可以正确地处理它所显示的所有数据类型。
小结
ListView 控件能够提供以多种方式显示数据的能力。它可以用于显示单独项目,也可以显示包含子项目信息的项目。使用由 ListView 控件提供的排序功能,您还可以使用户基于那些子项目排序 ListView 控件中的项目,无需考虑出现的数据类型。这种对项目及其子项目进行排序的能力使您的应用程序能够以 Microsoft® Windows® Explorer 和其他应用程序的用户所熟悉的方式表现其行为,它们提供数据的 ListView 显示和排序其内容的能力。