DataGridView 多列排序
一篇介绍按多列对 DataGridView 进行排序的方法的文章
引言
开箱即用的 DataGridView 只允许按单列排序。本文介绍了使 DataGridView 能够按多列对数据进行排序的代码。派生的 DataGridView 类可用于支持 IComparable 接口的任何数据类型(包括所有基本的 .NET 类型)。
该项目是使用 Visual C# 2005 Express Edition 构建的。
使用代码
普通代码使用
将排序的 DataGrid 视图添加到您的项目中
- 使用上面的链接下载库,并将 _SortedDataGridView.dll_ 解压到方便的位置。
- 通过右键单击工具箱并选择“选择项...”然后“浏览...”将您刚刚解压的 _SortedDataGridView.dll_ 添加到您的工具箱中。
- 现在,工具箱应该会显示 SortedDataGridView 作为新组件,您可以直接将其添加到您的窗体中,并以与普通 DataGridView 相同的方式进行配置。
SortedDataGridView 还有一个额外的属性:MaxSortColumns。此属性设置为允许用户排序网格的最大列数,或设置为 0 表示无限制。我发现按超过 3 列排序会令人困惑,因此倾向于将列数限制为 3 或 4 列,具体取决于显示的数据。我发现使用高级用户界面时很容易感到困惑,并且会丢失排序顺序的记录。
高级代码使用
如果您正在将现有项目转换为使用此代码,那么您可能会想使用此方法。
将库 _SortedDataGridView.dll_ 添加到您的项目中。
手动编辑窗体的 _Designer.cs_ 或 _Designer.vb_ 文件,将您的 DataGridView 的定义从 `System.Windows.Forms.DataGridView` 更改为 `Mobius.Utility.SortedDataGridView`。以同样的方式更改变量的实例化。
private void InitializeComponent()
{
this.PersonGrid = new Mobius.Utility.SortedDataGridView();
}
private Mobius.Utility.SortedDataGridView PersonGrid;
就是这样,您现在拥有一个按多列排序的 DataGridView。
用户界面
正常使用
要按 A、B 和 C 列排序,请按反序(C、B、A)单击列标题。
高级用法
在使用网格时,我发现能够反转其中一列的排序顺序而不改变其优先级很有用,因此我添加了代码来实现这一点。按住 Control 键即可访问此功能。
在按 A、B、C 排序后,按住 Control 键单击列标题即可反转其中一列的排序顺序。
这样做的副作用是,您可以通过按顺序单击列(同时按住 Control 键)来按 A、B、C 排序,因为如果一列尚未排序,它将被添加到排序列表中。
如果您已选择限制排序的列数,当您单击一个尚未排序的列,并且您当前正在按最大列数进行排序时,最后一列将被您刚刚单击的列替换。
关注点
DataGridView 接口
该代码重写了 `OnColumnHeaderMouseClick` 函数以启动排序,并使用实现了 IComparer 接口的自定义排序类重写了 `DataGridView.Sort` 函数。
出于两个原因,我将尽可能多的功能放入排序类中:
- 它更适合那里。
- 它使 DataGridView 类保持整洁。
原因 2 意味着将此处的附加功能添加到另一个现有的派生 DataGridView 类会容易得多。
排序顺序描述
网格公开了一个名为 `SortOrderDescription` 的属性,该属性返回一个已排序列的描述,适合用作列标题的工具提示或在状态栏中使用。演示项目在状态栏中显示了它的用法。
排序
排序使用一个派生的 IComparer 类 `DataGridComparer`,该类依次按每列进行排序。
代码很简单。首先,我将 IComparer 接口的对象参数进行转换,然后在单独的函数中进行比较。
public int Compare(object x, object y)
{
DataGridViewRow lhs = x as DataGridViewRow;
DataGridViewRow rhs = y as DataGridViewRow;
return Compare(lhs.Cells, rhs.Cells);
}
public int Compare(DataGridViewCellCollection lhs, DataGridViewCellCollection rhs)
{
foreach (SortColDefn colDefn in _sortedColumns)
{
int retval = Comparer.Default.Compare(
lhs[colDefn.colNum].Value,
rhs[colDefn.colNum].Value);
if (retval != 0)
return (colDefn.ascending ? retval : -retval);
}
// These two rows are indistinguishable.
return 0;
}
匿名委托
当用户请求对某一列进行排序时,代码需要确定该列是否已在排序中。如果是,则将其提升为主要排序列(在基本用户界面中,或在使用 Control 键时交换顺序)。
保存排序顺序的结构是 `SortColDefn`。
private struct SortColDefn
{
internal Int16 colNum;
internal bool ascending;
internal SortColDefn(int columnNum, SortOrder sortOrder)
{
colNum = Convert.ToInt16(columnNum);
ascending = (sortOrder != SortOrder.Descending);
}
}
当前排序的列存储在一个简单的数组中。
List<SortColDefn> _sortedColumns;
当您需要检查提供的列索引是否在数组中时,您可以简单地循环遍历数组并检查 `SortColDefn.colNum` 是否等于传递的列索引。
但是,使用 `List.FindIndex` 函数和匿名委托更有趣。
int sortPriority = _sortedColumns.FindIndex(
delegate(SortColDefn cd) { return cd.colNum == columnIndex; });
返回值 `sortPriority` 将为 `-1`(如果未找到 `columnIndex`),或者等于具有相同 `columnIndex` 的 `SortColDefn` 在 `_sortedColumns` 数组中的**索引**。
历史
版本 0.8 - 2007 年 4 月 11 日 - 首次发布,还有一半的路要走!