65.9K
CodeProject 正在变化。 阅读更多。
Home

可扩展的 ListViewSorter

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (12投票s)

2006年10月12日

CPOL

5分钟阅读

viewsIcon

64282

downloadIcon

1083

本文介绍如何使用两行代码对 ListView 进行升序/降序排序,以及如何扩展其排序功能。

Sample Image

引言

最近,我面临了一个令人兴奋的挑战:对 ListView 进行排序。于是我在 CodeProject 和其他网站上搜索,但发现我找到的大部分内容都过于复杂,不适合我简单的思维。我不想编写继承控件或在 Form 中写大量代码——仅仅是为了对 ListView 进行排序!

ListView 本身提供了一个排序功能,前提是将一个 IComparer 实现分配给 ListViewItemSorter 属性。所以一定有一个简单的方法可以使用这个功能。因此,我尝试自己寻找一个可扩展、易于使用的 ListViewSorter 的设计,最终,CodeProject 上 Michiel van Eerd 的文章 Extended ListView 启发了我。

接下来,您会发现一些解释和图示。如果您只想知道如何使用代码,请跳转到 使用代码 部分——那里更简短。最后,如果您想知道它为什么有效——请回到这里。

保修

本文及提供的代码**不声明完整或完美!**它只是尝试解决给定问题的一种方法。

设计

直到我发现上述文章,我才真正了解 Decimal.TryParse(String, Decimal)DateTime.TryParse(String, DateTime) 这两个函数。这些函数允许您尝试(**!**)将几乎任何内容转换为 DecimalDateTime,而不会遇到麻烦。即使这些方法有点不完美,但(在我看来)它们对于排序目的来说已经足够了。

有了这些方法的知识,我萌生了编写一组 ISortComparer 的想法,这些比较器可以在运行时分配给 ListView 的特定列。ISortComparer 接口通过 SortOrder 属性扩展了 IComparer

Sample Image

设计的初衷不是将比较器直接绑定到 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;
    }
  }
}

对上方代码的评论

  1. 如果没有要排序的内容,一切都相等。
  2. 处理至少一个值为空或空白的情况(这些方法已在 SortComparerBase 中实现)。
  3. DateTime.TryParse 方法会将(成功)结果写入这些变量。如果失败,这些变量的当前值不会被覆盖。所以将它们设置为暗示排序后位置的值。
  4. 如果 TryParse 方法失败(例如,因为值无法转换为所需类型),它将返回 false。如果发生这种情况,将返回 0(相等)。

这个连接 ISortComparerListView 的适配器是通过 ListViewSorter 类实现的,该类持有对 ListView 的引用,并处理 ListViewColumnClick 事件。它还为每列持有一个 ISortComparer 的字典。如前所述,ISortComparer 不知道名为 ListView 或其项目的类。因此,实现了一个中介或代理,它将 ListView 与特定的 ISortComparer 实现连接起来:ListViewItemComparer。此类持有待排序的列以及该列的指定 ISortComparer。由于它实现了 IComparer 接口,因此可以将其设置为 ListViewListViewItemSorter 属性。

ListViewSorter 的排序例程被 TreeViewColumnClick 事件调用时,会创建一个新的 ListViewItemComparer 实例,并由列比较器字典中的条目设置其 Comparer 属性。然后,将 ListViewItemComparer 设置为 ListViewListViewItemSorter 属性,并调用 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 提供了 ImageKeyUpImageKeyDown 属性来定义 ListViewSmallImageList 属性中图像的键。

可扩展性示例

如果您需要特定的比较器,编写自己的 ISortComparer 实现并扩展 ListViewSorter 的功能应该不是问题。在演示应用程序中,我实现了 GermanPostalCodeCityByCityComparerGermanPostalCodeCityByPostalCodeComparer(找不到更短的名字)作为示例。

在德国,每个地区都有一个独特的五位邮政编码。通常的做法是将地址的城市部分显示为邮政编码和城市名称的组合,中间用空格分隔(例如,“22307 汉堡”)。对这些组合进行排序有三种有意义的场景:

  1. 按完整字符串进行字母数字排序
  2. 按城市名称进行字母数字排序
  3. 按邮政编码进行数字排序

GermanPostalCodeCityByCityComparer 对应 b. 并按城市名称(“汉堡”,“柏林”,...)对项目进行排序。GermanPostalCodeCityByPostalCodeComparer 对应 c. 并按邮政编码(“01465”,“22307”,...)对项目进行数字排序。对于选项 c.,可以使用默认的 StringComparer

我猜这些比较器对大多数人来说不是绝对必要的,但它们可以提供 ISortComparer 可能有多大用处的想法。如果您阅读了代码,您就会明白增强排序功能是多么容易。

挑战:尚未实现按名称排序然后按邮政编码排序或反之的比较器。或者可以按特定顺序对多个列进行排序的比较器...

关注点

SortOrder:在 System.Windows.Forms 命名空间中有一个同名的枚举。由于使用诸如排序顺序之类的东西不应强制导入该命名空间,因此有必要在一个更常见的命名空间中包含一个 SortOrder 枚举。与微软一样,我认为 SortOrder 完美地描述了其含义,因此我没有将其命名为 SortOrderX

命名空间:如上文某处所述,我的初衷不是将比较器直接绑定到 ListViewSystem.Windows.Forms 命名空间。因此,我将它们与 ListViewSorter 分离到它们自己的命名空间中。

展望

本文描述的机制应该足以对 Collection 进行排序。如果时间允许,我将更新本文或撰写另一篇文章来讨论此主题。

历史

  • 2006-10-12 - 首次发布。
© . All rights reserved.