C#: DataGridView的自定义排序

21
我需要对一个DataGridView进行自然排序(像资源管理器一样),以便在同一列中数字和文本可以自然排序,而不是按字母顺序排序(例如,“place 3”排在“place 20”之前等)。 我有一个DataGridView,已将DataView设置为DataSource。 DataView包含从数据库创建的一些值的DataTable。 列类型为字符串。我有一个IComparer,它可以正常工作,但我无法弄清楚如何使用它,因为我找不到如何进行排序。 DataGridView.SortCompare事件非常完美,但它不起作用,因为它是数据绑定的。DataView.Sort只接受带有列名称和排序方式的字符串。
非常烦人。尝试在StackOverflow上阅读相关问题,并且搜索了很多谷歌,但我实际上找不到太多关于这个问题的内容。我真正发现的唯一东西是使用dataview的Sort(string)方法,但它无法正常工作,因为它按字母顺序排序。
有没有人知道如何在不太麻烦的情况下解决这个问题? 除了我之外还有其他人在与这个问题斗争吗?我真的不想重新实现整个datagridview或dataview类,只是为了获得自定义排序...
更新: 如果有人想知道,我仍在寻找一个好的答案来解决这个问题。虽然在此期间,我最终创建了自己的简单表类,然后手动将其提供给datagridview。重写SortCompare方法。比较烦人,但并不太难,因为我只需要显示值(无需编辑或其他操作),因此可以将所有内容转换为字符串。

我也在寻找解决方案。和你得出了相同的结论,尽管我需要在许多地方使用它,并且不能承担将所有数据表馈送到中间表中以提供此功能的成本,特别是因为用户可以决定按哪一列排序。到目前为止还没有运气,但我会继续尝试,并且很想看到答案。我无法想象为什么没有其他人在网络上询问或评论这个问题。 - lc.
你最近有发现任何事情吗? - Gad
@m_oLogin - 不幸的是,没有。但我已经转到了另一个项目,所以没有再去寻找它。不过我还是很好奇它是否存在,因为在另一个项目中可能会需要用到。 - Svish
尽管这是一个相当古老的问题,但最近我发现自己陷入了与@Svish相同的困境(数据绑定的GridView控件,无法访问SortCompare事件,并且需要在某些列上进行自定义排序-不仅仅是基本字母顺序)。经过一段时间的研究,我认为你在更新中提出的解决方案仍然是迄今为止最好、最优雅的解决方案。 - Fabien Teulieres
5个回答

5
请查看这个MSDN页面这篇博客文章。原则上,您需要在数据源(无论是ObjectDataSource还是SqlDataSource)中配置排序,而不是在GridView中进行配置。
据我所知,DataView类仅支持简单的升序/降序排序。如果没有看到您加载和绑定数据的代码,很难做出具体的建议,但您可以选择以下两种方法之一:
1.将数据加载到List中而不是DataTable中,调用Sort方法并传入比较方法,然后绑定到该列表。
2.在aspx代码中创建一个ObjectDataSource,直接从类中获取数据,并配置该ObjectDataSource使用您的IComparer。

是的,我有点明白了。但是你如何配置DataView的排序呢?我在DataView上唯一看到的排序方式是Sort方法,它需要一个包含列和方向的字符串。我找不到指定IComparer或类似内容的方法。 - Svish

3

这段代码应该可以正常运行。它类似于 ListView 的 ListViewItemSorter,使用 IComparer 接口。

使用方法:

private void dgv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    MyDataGridHelper.DataGridSort(dgv, e.ColumnIndex);
}

MyDataGridHelper.cs:

public class MyDataGridHelper
{
    public static void DataGridSort(DataGridView dgv, int column)
    {
        DataGridViewCustomSorter dgvSorter = null;
        if (dgv.Tag == null || !(dgv.Tag is IComparer))
        {
            dgvSorter = new DataGridViewCustomSorter(dgv);
            dgv.Tag = dgvSorter;
        }
        else
        {
            dgvSorter = (DataGridViewCustomSorter)dgv.Tag;
        }
        dgvSorter.SortColumn = column;
        dgv.Sort(dgvSorter);
    }

    private class DataGridViewCustomSorter : IComparer
    {
        private int ColumnIndex;
        private SortOrder OrderOfSort;
        private DataGridView myDataGridView;
        private TypeCode mySortTypeCode;

        public DataGridViewCustomSorter(DataGridView dgv)
        {
            myDataGridView = dgv;
            mySortTypeCode = Type.GetTypeCode(Type.GetType("System.String"));
            ColumnIndex = 0;
            OrderOfSort = SortOrder.None;
        }

        public int Compare(object x, object y)
        {
            int result;
            DataGridViewRow dgvX, dgvY;

            dgvX = (DataGridViewRow)x;
            dgvY = (DataGridViewRow)y;
            string sx = dgvX.Cells[ColumnIndex].Value.ToString();
            string sy = dgvY.Cells[ColumnIndex].Value.ToString();

            //null handling
            if (sx == String.Empty && sy == String.Empty)
                result = 0;
            else if (sx == String.Empty && sy != String.Empty)
                result = -1;
            else if (sx != String.Empty && sy == String.Empty)
                result = 1;
            else
            {
                switch (mySortTypeCode)
                {
                    case TypeCode.Decimal:
                        Decimal nx = Convert.ToDecimal(sx);
                        Decimal ny = Convert.ToDecimal(sy);
                        result = nx.CompareTo(ny);
                        break;
                    case TypeCode.DateTime:
                        DateTime dx = Convert.ToDateTime(sx);
                        DateTime dy = Convert.ToDateTime(sy);
                        result = dx.CompareTo(dy);
                        break;
                    case TypeCode.String:
                        result = (new CaseInsensitiveComparer()).Compare(sx, sy);
                        break;
                    default:
                        result = (new CaseInsensitiveComparer()).Compare(sx, sy);
                        break;
                }
            }
            if (OrderOfSort == SortOrder.Descending)
                result = (-result);

            return result;
        }

        public int SortColumn
        {
            set
            {
                if (ColumnIndex == value)
                {
                    OrderOfSort = (OrderOfSort == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending);
                }
                ColumnIndex = value;
                try
                {
                    mySortTypeCode = Type.GetTypeCode(Type.GetType((myDataGridView.Columns[ColumnIndex]).Tag.ToString()));
                }
                catch
                {
                    mySortTypeCode = TypeCode.String;
                }
            }
            get { return ColumnIndex; }
        }

        public SortOrder Order
        {
            set { OrderOfSort = value; }
            get { return OrderOfSort; }
        }
    } //end class DataGridViewCustomSorter
} //end class MyDataGridHelper

1
即使它能工作,它肯定缺乏解释,无法作为一个合理的好答案。 - Paul Kertscher

1

1
正如原帖中提到的那样,这种方法不适用于数据绑定网格,因此是无用的。 - EFraim

0

您可以创建2个隐藏列。将文本部分分配给第1个隐藏列,将数字部分分配给第2个隐藏列。现在按这些隐藏列进行排序(第1列采用字母排序,第2列采用数字排序)。

通过这种方式,您可以保留原始列以供显示目的,并拥有2个隐藏列进行排序。


但是,除非数据极度规范化和可预测,否则这样做效果不会很好... - lc.

0

你可以将排序逻辑移至数据库查询中,并返回一个包含正确排序顺序的附加列。

然后(类似于@True C Sharp的答案),您可以有一个包含此值的隐藏列,并按此列而不是显示列进行排序。

这假设确定排序顺序的逻辑可以在SQL中执行。如果确定排序顺序的算法很复杂,则可能无法使用此方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接