可扩展的 ListViewSorter






4.65/5 (12投票s)
本文介绍如何使用两行代码对 ListView 进行升序/降序排序,以及如何扩展其排序功能。
引言
最近,我面临了一个令人兴奋的挑战:对 ListView
进行排序。于是我在 CodeProject 和其他网站上搜索,但发现我找到的大部分内容都过于复杂,不适合我简单的思维。我不想编写继承控件或在 Form 中写大量代码——仅仅是为了对 ListView
进行排序!
ListView
本身提供了一个排序功能,前提是将一个 IComparer
实现分配给 ListViewItemSorter
属性。所以一定有一个简单的方法可以使用这个功能。因此,我尝试自己寻找一个可扩展、易于使用的 ListViewSorter 的设计,最终,CodeProject 上 Michiel van Eerd 的文章 Extended ListView 启发了我。
接下来,您会发现一些解释和图示。如果您只想知道如何使用代码,请跳转到 使用代码 部分——那里更简短。最后,如果您想知道它为什么有效——请回到这里。
保修
本文及提供的代码**不声明完整或完美!**它只是尝试解决给定问题的一种方法。
设计
直到我发现上述文章,我才真正了解 Decimal.TryParse(String, Decimal)
或 DateTime.TryParse(String, DateTime)
这两个函数。这些函数允许您尝试(**!**)将几乎任何内容转换为 Decimal
或 DateTime
,而不会遇到麻烦。即使这些方法有点不完美,但(在我看来)它们对于排序目的来说已经足够了。
有了这些方法的知识,我萌生了编写一组 ISortComparer
的想法,这些比较器可以在运行时分配给 ListView
的特定列。ISortComparer
接口通过 SortOrder
属性扩展了 IComparer
。
设计的初衷不是将比较器直接绑定到 ListView
,最终它们应该可以用于所有类型的集合。因此,它们独立于 System.Windows.Forms
命名空间,需要一个适配器机制。
为了让事情更简单,ISortComparer
实现可以继承自抽象类 SortComparerBase
,它提供了通用功能,例如保存 SortOrder
属性或处理 null
值。必须由具体实现来重写抽象方法 Compare
。
例如,对 DateComparer
的简要介绍
public class DateComparer : SortComparerBase{
public override int Compare(object x, object y)
{
// 1.
if (base.sortorder == SortOrder.None)
return 0;
// 2.
if (base.HasEmptyValue(x, y))
return base.CompareEmpty(x, y);
// 3.
DateTime x1 = DateTime.MinValue;
DateTime y1 = DateTime.MinValue;
// 4.
if (DateTime.TryParse(x.ToString(), out x1) &&
DateTime.TryParse(y.ToString(), out y1))
{
// 5.
if (base.sortorder == SortOrder.Ascending)
return DateTime.Compare(x1, y1);
else
return DateTime.Compare(y1, x1);
}
else
{
return 0;
}
}
}
对上方代码的评论
- 如果没有要排序的内容,一切都相等。
- 处理至少一个值为空或空白的情况(这些方法已在
SortComparerBase
中实现)。 DateTime.TryParse
方法会将(成功)结果写入这些变量。如果失败,这些变量的当前值不会被覆盖。所以将它们设置为暗示排序后位置的值。- 如果
TryParse
方法失败(例如,因为值无法转换为所需类型),它将返回false
。如果发生这种情况,将返回 0(相等)。
这个连接 ISortComparer
和 ListView
的适配器是通过 ListViewSorter
类实现的,该类持有对 ListView
的引用,并处理 ListView
的 ColumnClick
事件。它还为每列持有一个 ISortComparer
的字典。如前所述,ISortComparer
不知道名为 ListView
或其项目的类。因此,实现了一个中介或代理,它将 ListView
与特定的 ISortComparer
实现连接起来:ListViewItemComparer
。此类持有待排序的列以及该列的指定 ISortComparer
。由于它实现了 IComparer
接口,因此可以将其设置为 ListView
的 ListViewItemSorter
属性。
当 ListViewSorter
的排序例程被 TreeView
的 ColumnClick
事件调用时,会创建一个新的 ListViewItemComparer
实例,并由列比较器字典中的条目设置其 Comparer
属性。然后,将 ListViewItemComparer
设置为 ListView
的 ListViewItemSorter
属性,并调用 ListView.Sort()
。就这样。
使用代码
经过理论上的长篇大论,现在是实践上的言简意赅。实际上,只需要两行代码就可以使用任意列对 ListView
进行排序。
public partial class Form1 : Form
{
// Required line 1: A ListViewSorter member
ListViewSorter listviewsorter = new ListViewSorter();
//...
private void Form1_Load(object sender, EventArgs e)
{
// Required line 2: Set the ListViewSorters ListView property
listviewsorter.ListView = this.listView1;
// Optional: Register specific comparers for each column
listviewsorter.ColumnComparerCollection["Int"] = new NumericComparer();
listviewsorter.ColumnComparerCollection["Decimal"] = new NumericComparer();
listviewsorter.ColumnComparerCollection["Date"] = new DateComparer();
}
//...
}
注意:在上面的示例中,省略了 String
列的比较器,因为 StringComparer
是默认比较器。
如果您想在列标题上显示特定的图像来指示排序列和排序顺序,ListViewSorter
提供了 ImageKeyUp
和 ImageKeyDown
属性来定义 ListView
的 SmallImageList
属性中图像的键。
可扩展性示例
如果您需要特定的比较器,编写自己的 ISortComparer
实现并扩展 ListViewSorter
的功能应该不是问题。在演示应用程序中,我实现了 GermanPostalCodeCityByCityComparer
和 GermanPostalCodeCityByPostalCodeComparer
(找不到更短的名字)作为示例。
在德国,每个地区都有一个独特的五位邮政编码。通常的做法是将地址的城市部分显示为邮政编码和城市名称的组合,中间用空格分隔(例如,“22307 汉堡”)。对这些组合进行排序有三种有意义的场景:
- 按完整字符串进行字母数字排序
- 按城市名称进行字母数字排序
- 按邮政编码进行数字排序
GermanPostalCodeCityByCityComparer
对应 b. 并按城市名称(“汉堡”,“柏林”,...)对项目进行排序。GermanPostalCodeCityByPostalCodeComparer
对应 c. 并按邮政编码(“01465”,“22307”,...)对项目进行数字排序。对于选项 c.,可以使用默认的 StringComparer
。
我猜这些比较器对大多数人来说不是绝对必要的,但它们可以提供 ISortComparer
可能有多大用处的想法。如果您阅读了代码,您就会明白增强排序功能是多么容易。
挑战:尚未实现按名称排序然后按邮政编码排序或反之的比较器。或者可以按特定顺序对多个列进行排序的比较器...
关注点
SortOrder
:在 System.Windows.Forms
命名空间中有一个同名的枚举。由于使用诸如排序顺序之类的东西不应强制导入该命名空间,因此有必要在一个更常见的命名空间中包含一个 SortOrder
枚举。与微软一样,我认为 SortOrder
完美地描述了其含义,因此我没有将其命名为 SortOrderX
。
命名空间:如上文某处所述,我的初衷不是将比较器直接绑定到 ListView
和 System.Windows.Forms
命名空间。因此,我将它们与 ListViewSorter
分离到它们自己的命名空间中。
展望
本文描述的机制应该足以对 Collection
进行排序。如果时间允许,我将更新本文或撰写另一篇文章来讨论此主题。
历史
- 2006-10-12 - 首次发布。