使用 Windows Form 按列排序 ListView 项目
摘要: 说明如何根据所单击的列在 Microsoft .NET 中的 ListView 控件提供项目排序。
简介
ListView 控件是显示文件系统信息和显示 XML 或数据库数据的非常好的方式。ListView 控件通常用于显示表示项目以及项目文本的图形图标。此外,ListView 控件还可以用于显示有关子项目中项目的其他信息。例如,如果 ListView 控件显示一列文件,您可以配置 ListView 控件来显示作为子项目的诸如文件大小和属性的详细信息。要显示 ListView 控件中的子项目信息,必须将 View 属性设置为 View.Details。此外,您必须创建 ColumnHeader 对象并将这些对象分配给 ListView 控件的 Columns 属性。在设置这些属性后,项目以行和列格式进行显示,类似于 DataGrid 控件。以这种方式显示项目的能力使 ListView 控件为从任意类型的数据源显示数据提供了快速、简便的解决方案。
对 ListView 控件进行排序是通过使用 ListView 的 Sorting 属性而提供的。这使您可以定义要应用到项目的排序类型。如果您只想按项目排序,这是一个非常好的功能。如果您要按子项目排序,必须使用 ListView 控件的自定义排序功能。本文将说明如何在 ListView 控件中执行自定义排序,以及在排序时如何处理特殊的数据类型条件。
[url=http://www.microsoft.com/china/MSDN/library/netFramework/netframework/NFdnwinformslistviewsort.mspx#top][/url]
ListView 控件的自定义排序功能
ListView 控件提供了您可以使用排序的功能,而不是由 Sorting 属性提供。当 ListView 控件使用 Sorting 属性排序项目时,它使用一个实现 System.Collections.IComparer 接口的类。这个类提供用于排序每个项目的排序功能。为了按子项目进行排序,您必须创建自己的类,来实现反过来可以实现 ListView 控件所需排序的 IComparer 接口。该类利用构造函数进行定义,该构造函数可以指定 ListView 控件排序所用的列。在您创建这个类后(通常将其作为窗体的嵌套类),您可以创建该类的一个实例,并将其分配到 ListView 的 ListViewItemSorter 属性。当调用 Sort 方法时,这会确定 ListView 控件将要使用的自定义排序类。Sort 方法执行 ListView 项目的实际排序。
升序排序
以下部分提供的基本示例说明了在 ListView 控件中基于其子项目的排序。该示例说明了以升序排序 ListView 控件中的项目。升序排序或降序排序将会在本文的稍后部分进行说明。此处的目标就是说明在 ListView 控件中进行自定义排序的基本要求。
初始化控件
如果要开始,请创建 ListView 控件的实例,并将其添加到窗体中。在控件位于窗体上后,使用 Items 属性将项目添加到 ListView 控件。您可以添加任意多的项目,只要确保每个项目的文本都是唯一的。在您创建项目时,为每个项目添加两个子项目。第一个子项目应该包含数字信息,第二个子项目包含日期信息。下面的表格示例说明该信息在 ListView 控件中可能如何显示。
项目
子项目 1
子项目 2
Alpha
1.0
4/5/1945
Charlie
3.5
1/9/1920
Bravo
2.4
12/8/1930
创建两个 ColumnHeader 对象,并将它们分配到 ListView 控件的 Columns 属性中。将 View 属性设置为 View.Details。
处理 ColumnClick 事件
为了确定按哪个子项目集进行排序,您需要了解用户何时单击某个子项目的列标题。为此,您需要为 ListView 的 ColumnClick 事件创建一个事件处理方法。将事件处理方法作为窗体中的一个成员,并确保它包含的签名相似于下面代码示例所显示的签名。
'Visual Basic
Private Sub listView1_ColumnClick(sender As Object, e As System.Windows.Forms.ColumnClickEventArgs)
End Sub
//C#
private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
}
通过向窗体的构造函数中添加代码,将事件处理方法连接到 ListView 控件,如下面的示例所示。
'Visual Basic
AddHandler listView1.ColumnClick, AddressOf Me.listView1_ColumnClick
//C#
this.listView1.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.listView1_ColumnClick);
将下面的代码添加到用于 ColumnClick 事件的事件处理方法。
'Visual Basic
' Set the ListViewItemSorter property to a new ListViewItemComparer
' object.
Me.listView1.ListViewItemSorter = New ListViewItemComparer(e.Column)
' Call the sort method to manually sort.
listView1.Sort()
//C#
// Set the ListViewItemSorter property to a new ListViewItemComparer
// object.
this.listView1.ListViewItemSorter = new ListViewItemComparer(e.Column);
// Call the sort method to manually sort.
listView1.Sort();
添加到事件处理方法的代码会利用 ListViewItemComparer 类(在下一部分中定义)的新实例来设置 ListView 控件的 ListViewItemSorter 属性,然后分配要单击的列。被单击的列作为事件参数的组成部分进行传递。在设置 ListViewItemSorter 属性后,调用 Sort 方法来执行手动排序。
创建 ListViewItemComparer 类
正如前面提到的那样,在 ListView 控件中进行自定义排序的关键在于创建实现 System.Collections.IComparer 接口的类。就是这个类为项目提供排序。对于这个示例,定义了名为 ListViewItemComparer 的类,并且将其添加为窗体的嵌套类。ListViewItemComparer 执行传递到其构造函数的指定列的基本升序排序。将以下类定义添加到 Form 类并确保它在窗体内是正确嵌套的。
'Visual Basic
' Implements the manual sorting of items by column.
Class ListViewItemComparer
Implements IComparer
Private col As Integer
Public Sub New()
col = 0
End Sub
Public Sub New(column As Integer)
col = column
End Sub
Public Function Compare(x As Object, y As Object) As Integer _
Implements System.Collections.IComparer.Compare
Dim returnVal as Integer = -1
returnVal = [String].Compare(CType(x, _
ListViewItem).SubItems(col).Text, _
CType(y, ListViewItem).SubItems(col).Text)
Return returnVal
End Function
End Class
//C#
// Implements the manual sorting of items by column.
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 控件中执行基本手动项目排序所需要的基本要素。接下来的部分会扩展这个示例,提供升序和降序排序功能。
升序或降序排序
ListView 控件的用户将期望具有同时以升序和降序排序项目的功能。为了实现这个功能,需要对前面的示例进行某些改动,以便使 Compare 方法可以确定要排序的项目。
对窗体的更改
通常情况下,要在升序和降序排序之间切换,您要多次单击列标题。用户期望如果他们单击列标题,排序将会发生,随后再次单击将更改排序顺序。前面的代码示例需要能够确定何时多次单击列。为此,您可以将一个私有整数变量添加到 Form 类。这个变量存储上一次单击的列。ColumnClick 事件处理方法将使用这个变量来比较上一次的列与当前单击的列,并确定它们是否相同。将以下成员定义添加到 Form 类中。
'Visual Basic
Dim sortColumn as Integer = -1
//C#
private int sortColumn = -1;
ColumnClick 事件处理方法的更改
在前面的示例中定义的 ColumnClick 事件处理方法需要进行修改,以便跟踪单击过的列和当前排序顺序。添加以下代码以替换在前面的示例中创建的 ColumnClick 事件处理方法中的代码。
'Visual Basic
Private Sub listView1_ColumnClick(sender As Object, e As
System.Windows.Forms.ColumnClickEventArgs)
' Determine whether the column is the same as the last column clicked.
If e.Column <> sortColumn Then
' Set the sort column to the new column.
sortColumn = e.Column
' Set the sort order to ascending by default.
listView1.Sorting = SortOrder.Ascending
Else
' Determine what the last sort order was and change it.
If listView1.Sorting = SortOrder.Ascending Then
listView1.Sorting = SortOrder.Descending
Else
listView1.Sorting = SortOrder.Ascending
End If
End If
' Call the sort method to manually sort.
listView1.Sort()
' Set the ListViewItemSorter property to a new ListViewItemComparer
' object.
listView1.ListViewItemSorter = New ListViewItemComparer(e.Column, _
listView1.Sorting)
End Sub
//C#
private void listView1_ColumnClick(object sender,
System.Windows.Forms.ColumnClickEventArgs e)
{
// Determine whether the column is the same as the last column clicked.
if (e.Column != sortColumn)
{
// Set the sort column to the new column.
sortColumn = e.Column;
// Set the sort order to ascending by default.
listView1.Sorting = SortOrder.Ascending;
}
else
{
// Determine what the last sort order was and change it.
if (listView1.Sorting == SortOrder.Ascending)
listView1.Sorting = SortOrder.Descending;
else
listView1.Sorting = SortOrder.Ascending;
}
// Call the sort method to manually sort.
listView1.Sort();
// Set the ListViewItemSorter property to a new ListViewItemComparer
// object.
this.listView1.ListViewItemSorter = new ListViewItemComparer(e.Column,
listView1.Sorting);
}
该代码在设置 ListViewItemSorter 属性和调用 Sort 方法之前添加了一些逻辑。增加的代码确定当前单击的项目与上一次已经单击过的列是否相同。如果不同,就会设置 sortColumn 变量,并且将 SortOrder.Ascending 值分配给 Sorting 属性。如果 sortColumn 变量和当前单击的列相同,则 Sorting 属性将更改为相反的排序顺序。在这个示例中,Sorting 属性用作定义项目排序顺序的方法。因为您在使用自定义的比较程序类来排序项目,所以设置 Sorting 属性对排序操作没有任何影响。在该示例中,它只是简单地用作一个变量。
在指定排序顺序后,对 ColumnClick 事件处理方法代码的唯一其他更改就是将附加参数值添加到 ListViewItemComparer 对象的创建中,而该对象是您要分配到 ListViewItemSorter 属性的对象。正如这个示例的后面部分所示,ListViewItemComparer 类包含一个指定排序顺序的新参数。您使用 ListView.Sorting 属性的值,将值分配给该参数。
ListViewItemComparer 类的更改
使该示例可以以升序或降序进行排序的上面的一系列更改就是对 ListViewItemComparer 类的更改。增加的代码将执行以两种排序模式之一比较项目所需的逻辑。添加以下代码以替换在前面的示例中为 ListViewItemComparer 定义的代码。
'Visual Basic
' Implements the manual sorting of items by columns.
Class ListViewItemComparer
Implements IComparer
Private col As Integer
Private order as SortOrder
Public Sub New()
col = 0
order = SortOrder.Ascending
End Sub
Public Sub New(column As Integer, order as SortOrder)
col = column
Me.order = order
End Sub
Public Function Compare(x As Object, y As Object) As Integer _
Implements System.Collections.IComparer.Compare
Dim returnVal as Integer = -1
returnVal = [String].Compare(CType(x, _
ListViewItem).SubItems(col).Text, _
CType(y, ListViewItem).SubItems(col).Text)
' Determine whether the sort order is descending.
If order = SortOrder.Descending Then
' Invert the value returned by String.Compare.
returnVal *= -1
End If
Return returnVal
End Function
End Class
//C#
// Implements the manual sorting of items by columns.
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);
// Determine whether the sort order is descending.
if(order == SortOrder.Descending)
// Invert the value returned by String.Compare.
returnVal *= -1
return returnVal;
}
}33
该代码将排序顺序参数添加到构造函数,并且创建一个用于存储该值的私有变量。Compare 方法的代码已更改,以便确定排序顺序是否为降序。如果是,则将String.Compare 方法的返回值乘以 -1 以更改该值,这样 Compare 方法返回的值就与 String.Compare 返回的值相反。
运行该代码并单击列。该列以升序顺序进行排列。单击相同的列,列就会以降序进行排序。同样,日期列没有正确的排序,因为它被存储为一个字符串而不是日期。在接下来的部分中,通过添加功能来按日期或者按字符串进行排序(这取决于数据的类型),从而完成该示例。
排序日期
作为项目而置于 ListView 控件中的数据显示为文本并以文本的形式进行存储。这使得使用 IComparer 类中的 String.Compare 方法进行排序变得非常简单。String.Compare 可以对字母字符和数字进行排序。但是,使用 String.Compare 不能对特定数据类型进行正确排序,例如日期和时间信息。因此,System.DateTime 结构具有一个与 String 类相同作用的 Compare 方法。这个方法可以用来基于时间顺序执行相同类型的排序。在本部分中,您只需修改 Compare 方法就可以正确排序日期。
添加以下代码以替换为 Compare 方法(该方法是前面示例中定义的 ListViewItemComparer 类的方法)而定义的代码。
'Visual Basic
Public Function Compare(ByVal x As Object, ByVal y As Object) As
Integer Implements System.Collections.IComparer.Compare
Dim returnVal As Integer
' Determine whether the type being compared is a date type.
Try
' Parse the two objects passed as a parameter as a DateTime.
Dim firstDate As System.DateTime = DateTime.Parse(CType(x, _
ListViewItem).SubItems(col).Text)
Dim secondDate As System.DateTime = DateTime.Parse(CType(y, _
ListViewItem).SubItems(col).Text)
' Compare the two dates.
returnVal = DateTime.Compare(firstDate, secondDate)
' If neither compared object has a valid date format,
' compare as a string.
Catch
' Compare the two items as a string.
returnVal = [String].Compare(CType(x, _
ListViewItem).SubItems(col).Text, CType(y,ListViewItem).SubItems(col).Text)
End Try
' Determine whether the sort order is descending.
If order = SortOrder.Descending Then
' Invert the value returned by String.Compare.
returnVal *= -1
End If
Return returnVal
End Function
//C#
public int Compare(object x, object y)
{
int returnVal;
// Determine whether the type being compared is a date type.
try
{
// Parse the two objects passed as a parameter as a DateTime.
System.DateTime firstDate =
DateTime.Parse(((ListViewItem)x).SubItems[col].Text);
System.DateTime secondDate =
DateTime.Parse(((ListViewItem)y).SubItems[col].Text);
// Compare the two dates.
returnVal = DateTime.Compare(firstDate, secondDate);
}
// If neither compared object has a valid date format, compare
// as a string.
catch
{
// Compare the two items as a string.
returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
((ListViewItem)y).SubItems[col].Text);
}
// Determine whether the sort order is descending.
if (order == SortOrder.Descending)
// Invert the value returned by 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 显示和排序其内容的能力。