我们在开发Windows Form 应用程序时,往往需要使用ListView控件——通过设置其View属性为Details——来显示列表形式的详细数据。遗憾的是,.NET自带的这个ListView控件并没有提供一些我们常用的功能,例如:单击列头实现文本升降序排序、滚动条滚动事件等等。
得益于.NET强大的面向对象的支持,我们可以继承ListView并加入我们想要实现的功能,从而实现ListView的扩展。
今天讨论一下在C#中如何实现“单击列头实现排序”的功能。部分参考http://msdn.microsoft.com/zh-cn/library/system.windows.forms.listview.listviewitemsorter.aspx
一、 知识准备
要实现排序功能,首先我们得了解ListView以及.NET本身自带的一些关于排序、比较方面的接口和对象的属性、方法或事件。
ListView.ListViewItemSorter 属性: 获取或设置用于控件的排序比较器。
该属性类型为System.Collections.IComparer,顾名思义,这是一个实现对象比较的接口,其中只定义了一个方法:int Compare(object x, object y);通过实现该方法来比较两个对象并返回比较结果。
二、 实现思路
我们知道,为ListViewItemSorter属性赋值后,则ListView自动调用Sort方法实现排序。
既然ListViewItemSorter的类型为System.Collections.IComparer,那么我们自定义一个类并实现IComparer接口,然后在列单击事件里面将该类的实例赋给ListViewItemSorter属性即可。需要说明的是,传入方法:int Compare(object x, object y)的参数x和y均是ListViewItem类型的对象。
三、 具体实现
1. 定义一个类ListViewEx继承于ListView。
public class ListViewEx : ListView
2. 在该类中定义一个私有类ListViewItemComparer,并且该类实现System.Collections.IComparer接口。
private class ListViewItemComparer : System.Collections.IComparer
3. 因为方法:int Compare(object x, object y)的参数x和y均是ListViewItem类型的对象,所以要实现某列的排序,就必须有该列的index值。我们通过构造函数传参的方式将列的index传入。
private int _column;
public ListViewItemComparer(int column)
{
_column = column;
}
4. 关键步骤1:实现int Compare(object x, object y)方法。
public int Compare(object x, object y)
{
return (string.Compare(((ListViewItem)x).SubItems[_column].Text, ((ListViewItem)y).SubItems[_column].Text));
}
5. 关键步骤2:重写(override)ListView的OnColumnClick方法。
protected override void OnColumnClick(ColumnClickEventArgs e)
{
base.OnColumnClick(e);
this.ListViewItemSorter = new ListViewItemComparer(e.Column);
}
6. 全部代码,这里省去VS自动生成的部分——InitializeComponent()。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ListViewExtand
{
public partial class ListViewEx : ListView
{
public ListViewEx()
{
InitializeComponent();
}
public ListViewEx(IContainer container)
{
container.Add(this);
InitializeComponent();
}
protected override void OnColumnClick(ColumnClickEventArgs e)
{
base.OnColumnClick(e);
this.ListViewItemSorter = new ListViewItemComparer(e.Column);
}
private class ListViewItemComparer : System.Collections.IComparer
{
private int _column;
public ListViewItemComparer(int column)
{
this. _column = column;
}
#region IComparer Members
public int Compare(object x, object y)
{
return (-string.Compare(((ListViewItem)x).SubItems[_column].Text, ((ListViewItem)y).SubItems[_column].Text));
}
#endregion
}
}
}
四、 缺陷及改进
可以发现,这样做出来的ListView扩展控件,在单击列头时始终是按照文本升序进行排序。我们需要是这样的实用功能:某列头单击一次按文本升序排序,再次单击则按降序排序。
那么现在就拿上面的半成品继续改进。相信有了以上的基础,这次改进会比较容易,只需要理清实现思路即可:某列头单击一次按文本升序排序后,应该能够存储当前该列排序的状态(即列索引index值、排序方向),以便在再次单击时,能够改变该列的排序状态从而实现重新排序。
1. 在私有类中增加2个属性int SortColumn和SortOrder Order,同时自定义构造函数public ListViewItemComparer(int column)和public ListViewItemComparer(int column, SortOrder sortOrder)。
public ListViewItemComparer()
{
this.SortColumn = 0;
this.Order = SortOrder.None;
}
public ListViewItemComparer(int column)
: this()
{
this.SortColumn = column;
}
public ListViewItemComparer(int column, SortOrder sortOrder)
: this(column)
{
this.Order = sortOrder;
}
public int SortColumn
{
get;
set;
}
public SortOrder Order
{
get;
set;
}
2. 重新实现int Compare(object x, object y)方法:当获知当前按升序排序时(根据属性Order),返回对象x和y的比较结果;否则若是按降序排序,则返回对象x和y的比较结果的相反数;再则返回0,表示不排序。
public int Compare(object x, object y)
{
int result = string.Compare(((ListViewItem)x).SubItems[this.SortColumn].Text, ((ListViewItem)y).SubItems[this.SortColumn].Text);
if (this.Order == SortOrder.Ascending)
{
return result;
}
else if (this.Order == SortOrder.Descending)
{
return (-result);
}
else
{
return 0;
}
}
3. 在类ListViewEx中重新实现重写(override)ListView的OnColumnClick方法。
protected override void OnColumnClick(ColumnClickEventArgs e)
{
base.OnColumnClick(e);
if (this.ListViewItemSorter == null)
{
this.ListViewItemSorter = new ListViewItemComparer(e.Column, SortOrder.Ascending);
}
else
{
ListViewItemComparer comparer = this.ListViewItemSorter as ListViewItemComparer;
if (comparer.SortColumn == e.Column)
{
if (comparer.Order == SortOrder.Ascending)
{
comparer.Order = SortOrder.Descending;
}
else
{
comparer.Order = SortOrder.Ascending;
}
}
else
{
comparer.SortColumn = e.Column;
comparer.Order = SortOrder.Ascending;
}
//仅仅改变了ListViewItemSorter属性值,这里不会自动调用Sort()方法,需要显式指定执行Sort()方法实现排序。
this.Sort();
}
}
4. 全部代码,这里省去VS自动生成的部分——InitializeComponent()。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ListViewExtand
{
public partial class ListViewEx : ListView
{
public ListViewEx()
{
InitializeComponent();
}
public ListViewEx(IContainer container)
{
container.Add(this);
InitializeComponent();
}
protected override void OnColumnClick(ColumnClickEventArgs e)
{
base.OnColumnClick(e);
if (this.ListViewItemSorter == null)
{
this.ListViewItemSorter = new ListViewItemComparer(e.Column, SortOrder.Ascending);
}
else
{
ListViewItemComparer comparer = this.ListViewItemSorter as ListViewItemComparer;
if (comparer.SortColumn == e.Column)
{
if (comparer.Order == SortOrder.Ascending)
{
comparer.Order = SortOrder.Descending;
}
else
{
comparer.Order = SortOrder.Ascending;
}
}
else
{
comparer.SortColumn = e.Column;
comparer.Order = SortOrder.Ascending;
}
//仅仅改变了ListViewItemSorter属性值,这里不会自动调用Sort()方法,需要显式指定执行Sort()方法实现排序。
this.Sort();
}
}
private class ListViewItemComparer : System.Collections.IComparer
{
public ListViewItemComparer()
{
this.SortColumn = 0;
this.Order = SortOrder.None;
}
public ListViewItemComparer(int column)
: this()
{
this.SortColumn = column;
}
public ListViewItemComparer(int column, SortOrder sortOrder)
: this(column)
{
this.Order = sortOrder;
}
public int SortColumn
{
get;
set;
}
public SortOrder Order
{
get;
set;
}
#region IComparer Members
public int Compare(object x, object y)
{
int result = string.Compare(((ListViewItem)x).SubItems[this.SortColumn].Text, ((ListViewItem)y).SubItems[this.SortColumn].Text);
if (this.Order == SortOrder.Ascending)
{
return result;
}
else if (this.Order == SortOrder.Descending)
{
return (-result);
}
else
{
return 0;
}
}
#endregion
}
}
}
五、 小结
只需按照以上方法,自定义一个类实现IComparer接口,并将其实例赋给ListView的属性即可完美实现“点击列头排序”。
终于写完了,不知读者看过后有何感想,是否还有需要该进的地方?比如说类 ListViewItemComparer作为内部私有类这种处理方式的优缺点 ,等等欢迎讨论。