如何通过两个列对 DataGridView 进行排序

17

如何按照两个列进行排序(升序)?我有两列: daystatus

如果我需要按一列排序,我可以这样做:

this.dataGridView1.Sort (this.dataGridView1.Columns["day"], ListSortDirection.Ascending);

但是对于两个呢?


我认为你不能将它们一起排序,你可以调用你的代码对两列进行排序,首先按日期排序,然后再按另一列排序。 - Moonlight
1
你可以在这里查看:http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/thread/7f819d92-5bd9-4b5b-b08c-fb15bb5331c4/ - Adrian Fâciu
你是在使用WinForms还是其他什么? - Anatolii Gabuza
8个回答

15
如果你的 DataGridView 是数据绑定的,你可以按照下面的方式对 Datatable 视图进行排序并重新绑定到 datatable:
private DataGridView dataGridView1 = new DataGridView();
private BindingSource bindingSource1 = new BindingSource();

private void Form1_Load(object sender, System.EventArgs e)
{
    // Bind the DataGridView to the BindingSource        
    dataGridView1.DataSource = bindingSource1;
    SortDataByMultiColumns(); //Sort the Data
}

private void SortDataByMultiColumns()
{
    DataView view = dataTable1.DefaultView;
    view.Sort = "day ASC, status DESC"; 
    bindingSource1.DataSource = view; //rebind the data source
}

或者,不使用绑定源并直接绑定到 DataView:

private void SortDataByMultiColumns()
{
    DataView view = ds.Tables[0].DefaultView;
    view.Sort = "day ASC, status DESC"; 
    dataGridView1.DataSource = view; //rebind the data source
}

dataGridView1.DataSource = ds.Tables[0]; - user1112847
@user1112847 我不明白... 如果您问的是在bindingSource1的位置上使用ds.Tables[0]代码是否能正常工作,那么是的,我认为它应该能正常工作。 - S2S2

9
添加一个隐藏列,将这两个列合并在一起,并按此列排序。

3
这是一个好主意,但如果你想按一个列升序排列,另一个列降序排列,则行不通。 - Mohammad Sina Karvandi

8
你可以使用 DataGridView 的 Sort 方法,但需要指定一个实现了 IComparer 接口的类作为参数。
以下是这样一个类的示例:
public class MyTwoColumnComparer : System.Collections.IComparer
{
    private string _SortColumnName1;
    private int _SortOrderMultiplier1;
    private string _SortColumnName2;
    private int _SortOrderMultiplier2;

    public MyTwoColumnComparer(string pSortColumnName1, SortOrder pSortOrder1, string pSortColumnName2, SortOrder pSortOrder2)
    {
        _SortColumnName1 = pSortColumnName1;
        _SortOrderMultiplier1 = (pSortOrder1 == SortOrder.Ascending) ? 1 : -1;
        _SortColumnName2 = pSortColumnName2;
        _SortOrderMultiplier2 = (pSortOrder2 == SortOrder.Ascending) ? 1 : -1;
    }

    public int Compare(object x, object y)
    {
        DataGridViewRow r1 = (DataGridViewRow)x;
        DataGridViewRow r2 = (DataGridViewRow)y;

        int iCompareResult = _SortOrderMultiplier1 * String.Compare(r1.Cells[_SortColumnName1].Value.ToString(), r2.Cells[_SortColumnName1].Value.ToString());
        if (iCompareResult == 0) iCompareResult = _SortOrderMultiplier2 * String.Compare(r1.Cells[_SortColumnName2].Value.ToString(), r2.Cells[_SortColumnName2].Value.ToString());
        return iCompareResult;
    }
}

现在,我们可以在“Programmatic”排序模式下使用鼠标单击来调用此列:
private void dgvAllMyEmployees_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    DataGridViewColumn dgvcClicked = dgvAllEmployees.Columns[e.ColumnIndex];
    if (dgvcClicked.SortMode == DataGridViewColumnSortMode.Programmatic)
    {
        _SortOrder = (_SortOrder == SortOrder.Ascending) ? SortOrder.Descending : SortOrder.Ascending;
        MyTwoColumnComparer Sort2C = new MyTwoColumnComparer(dgvcClicked.Name, _SortOrder, "LastName", SortOrder.Ascending);
        dgvAllEmployees.Sort(Sort2C);
    }
}

类级别变量 _SortOrder 有助于跟踪排序顺序。可以进一步增强此功能,以记住最后两列点击的顺序,并按所需顺序对它们进行排序。


这是答案。 - Casey Crookston
转念一想...我无法让它正常工作。第一次单击列时,它会进行排序(升序),但如果我再次单击它,则不会按降序排序。 - Casey Crookston
1
这个答案让我非常接近了!!我添加了一个新的答案,基于这个答案并解决了当单击列时不能在升序和降序之间切换的问题。 - Casey Crookston
1
看起来非常有前途,但不幸的是,它似乎无法与数据绑定的DataGridView控件一起使用。 - Jim Wooley

5

TLDR; 我有一个两行代码的解决方案。

我也曾经需要做同样的事情,但在研究了所有这些复杂的方式之后,包括使用单独的.dll文件或编写自己的类/方法来完成,我知道肯定有更简单的方法。结果证明我是对的,因为我想出了一种只用两行代码就能完成的方法。这对我起作用了。

幸运的是,对我们来说,.NET Framework的Sort()方法确实可以帮助我们解决这个问题。思路是你想要单独排序每个列,但你排序它们的顺序会产生所需的输出。

例如,我有一个文件类型的列和一个文件名的列。每当我想按类型对数据进行排序时,我想要确保每个类型中的名称也按字母顺序排序。

目标:按类型排序也将按字母顺序排序文件名。

数据:

zxcv.css

testimg3.jpg

asdf.html

testimg2.jpg

testimg1.jpg

按名称排序数据:

mConflictsDataGridView.Sort(mConflictsDataGridView.Columns[mNameLabel.Index], ListSortDirection.Ascending);

asdf.html

testimg1.jpg

testimg2.jpg

testimg3.jpg

zxcv.css

如您所见,这将确保名称按顺序排序,以便当我按文件类型进行排序时,两个要求都将得到满足。

按文件类型对数据进行排序:

mConflictsDataGridView.Sort(mConflictsDataGridView.Columns[mFileExtensionLabel.Index], ListSortDirection.Ascending);

zxcv.css

asdf.html

testimg1.jpg

testimg2.jpg

testimg3.jpg

好了!排好序啦!

解决方案:对于您的情况,您可以尝试像下面这样的东西,并且您可能需要进一步调整它以适应您自己的代码。

DataGridView1.Sort(DataGridView1.Columns["status"], ListSortDirection.Ascending);
DataGridView1.Sort(DataGridView1.Columns["day"], ListSortDirection.Asscending);

这应该能够按日期显示您的结果,并将其状态字段排序。


如果你的示例中有一个名为testimg2.css的值,似乎它不会起作用。 - user3423149
这个不起作用。结果是网格将按照最后一次排序操作进行排序。第一次排序操作被第二次完全覆盖。 - blitz_jones

2

约翰·库尔茨提供的答案让我接近了解决方法。但是我发现当我单击一列时,它确实按照两列进行排序...在他的示例中:dgvcClicked.Name,“LastName”。所以,很好!

但是,如果我再次点击该列,则不会按相反的方向进行排序。因此,该列被固定为升序。

为了克服这个问题,我必须手动跟踪排序顺序。从这个类开始:

public class ColumnSorting
{
    public int ColumnIndex { get; set; }
    public ListSortDirection Direction { get; set; }
}

然后,我添加了这个全局作用域的列表:

List<ColumnSorting> _columnSortingList = new List<ColumnSorting>();

然后,在执行排序的方法中,我会:

  1. 检查要排序的列索引是否已经存在于_columnSortingList中。如果不存在,则将其添加。
  2. 如果已经存在,则交换排序顺序。

完成以上步骤后,你就成功了。


0

您可以尝试使用this,或者使用自定义排序:

private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (dataGridView1.Columns[e.ColumnIndex].HeaderText =="day")
            {
               myBindingSource.Sort = "day, hour";
            }
        }

dataGridView1.DataSource = ds.Tables[0]; - user1112847
ds.Tables[0].DefaultView.Sort = "day, hour"; ds.Tables[0].DefaultView.Sort = "day, hour"; - pistipanko

0
这是对@John Kurtz的IComparer类的改进,支持多列和按数字或日期排序。
    public static void SortOnMultipleColumns(DataGridView dgv, Dictionary<string /*Column Name*/, ColumnSortInfo> sortingColumns)
    {
        // Show the glyphs
        foreach (DataGridViewColumn col in dgv.Columns)
        {
            System.Windows.Forms.SortOrder sortOrder = System.Windows.Forms.SortOrder.None;

            foreach (var kvp in sortingColumns)
            {
                if (kvp.Key == col.Name)
                {
                    sortOrder = kvp.Value.SortOrder;
                    break;
                }
            }
            col.HeaderCell.SortGlyphDirection = sortOrder;
        }

        // Sort the grid
        MultiColumnCompararor multiColumnCompararor = new MultiColumnCompararor(sortingColumns);
        dgv.Sort(multiColumnCompararor);
    }

    public class ColumnSortInfo
    {
        public enum ValueConversion { ToString, ToNumber, ToDate}
        public ColumnSortInfo(System.Windows.Forms.SortOrder sortOrder, ValueConversion valueConversion = ValueConversion.ToString)
        {
            SortOrder = sortOrder;
            MyValueConversion = valueConversion;
            SortOrderMultiplier = (SortOrder == SortOrder.Ascending) ? 1 : -1;
        }
        public System.Windows.Forms.SortOrder SortOrder { get; set; }
        public int SortOrderMultiplier { get; }
        public ValueConversion MyValueConversion { get; set; }

        public static double StringToDouble(string sVal)
        {
            if (Double.TryParse(sVal, out double dVal))
            {
                return dVal;
            }
            return 0;
        }
        public static DateTime StringToDateTime(string sVal)
        {
            if (DateTime.TryParse(sVal, out DateTime dt))
            {
                return dt;
            }
            return DateTime.MinValue;
        }
    }
    private class MultiColumnCompararor : System.Collections.IComparer
    {

        IDictionary<string /*Column Name*/, ColumnSortInfo> _sortingColumns;

        public MultiColumnCompararor(IDictionary<string /*Column Name*/, ColumnSortInfo> sortingColumns)
        {
            _sortingColumns = sortingColumns;
        }

        public int Compare(object x, object y)
        {
            try
            {
                DataGridViewRow r1 = (DataGridViewRow)x;
                DataGridViewRow r2 = (DataGridViewRow)y;

                foreach (var kvp in _sortingColumns)
                {
                    string colName = kvp.Key;
                    ColumnSortInfo csi = kvp.Value;

                    string sVal1 = r1.Cells[colName].Value?.ToString().Trim()??"";
                    string sVal2 = r2.Cells[colName].Value?.ToString().Trim()??"";

                    int iCompareResult = 0;

                    switch (csi.MyValueConversion)
                    {
                        case ColumnSortInfo.ValueConversion.ToString:
                            iCompareResult = String.Compare(sVal1, sVal2);
                            break;
                        case ColumnSortInfo.ValueConversion.ToNumber:
                            double d1 = ColumnSortInfo.StringToDouble(sVal1);
                            double d2 = ColumnSortInfo.StringToDouble(sVal2);
                            iCompareResult = ((d1 == d2) ? 0 : ((d1 > d2) ? 1 : -1));
                            break;
                        case ColumnSortInfo.ValueConversion.ToDate:
                            DateTime dt1 = ColumnSortInfo.StringToDateTime(sVal1);
                            DateTime dt2 = ColumnSortInfo.StringToDateTime(sVal2);
                            iCompareResult = ((dt1 == dt2) ? 0 : ((dt1 > dt2) ? 1 : -1));
                            break;
                        default:
                            break;
                    }

                    iCompareResult = csi.SortOrderMultiplier * iCompareResult;

                    if (iCompareResult != 0) { return iCompareResult; }
                }
                return 0;
            }
            catch (Exception ex)
            {
                return 0;
            }
        }
    }

使用方法:

        Dictionary<String, ColumnSortInfo> sortingColumns = new Dictionary<String, ColumnSortInfo>
                        { {"policyNumber", new ColumnSortInfo(System.Windows.Forms.SortOrder.Ascending)},
                          {"MessageId", new ColumnSortInfo(System.Windows.Forms.SortOrder.Descending, ColumnSortInfo.ValueConversion.ToNumber)},
                          {"CreationDate", new ColumnSortInfo(System.Windows.Forms.SortOrder.Ascending, ColumnSortInfo.ValueConversion.ToDate)}};

        CsUtils.SortOnMultipleColumns(dgv, sortingColumns);

0

当处理绑定数据时,我使用这个解决方案。它适用于我们的用户,并显示当前的排序标准。所有排序都只按升序排列。

添加一个复选框、一个文本框、一个ColumnHeaderMouseClick事件和如下所示的代码。复选框将切换文本框的可见性,单击任何列标题将把排序标准添加到文本框中。要清除文本框,只需双击它即可。

        private void FooDataGridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if(chkMultiSort.Checked == true)
            {
                string columnHeader = FooDataGridView.Columns[e.ColumnIndex].DataPropertyName;
                txtMultiSort.Text += (columnHeader + ", ");
                try
                {
                    FooBindingSource.Sort = txtMultiSort.Text.Remove(txtMultiSort.Text.Length - 2);
                }
                catch
                {
                    MessageBox.Show("Invalid Sort Data", "Information", MessageBoxButtons.OK, MessageBoxIcon.None);
                    txtMultiSort.Text = String.Empty;
                }
            }

        }

        private void ChkMultiSort_CheckedChanged(object sender, EventArgs e)
        {
            if(chkMultiSort.Checked == true)
            {
                txtMultiSort.Visible = true;
            }
            else
            {
                txtMultiSort.Visible = false;
                txtMultiSort.Text = String.Empty;
            }
        }

        private void TxtMultiSort_DoubleClick(object sender, EventArgs e)
        {
            txtMultiSort.Text = String.Empty;
        }


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