【问题标题】:Sort a DataGridView on multiple columns?在多列上对 DataGridView 进行排序?
【发布时间】:2011-06-06 12:31:00
【问题描述】:

我搜索了一个在多列上对 DataGridView 进行排序的示例,但似乎无法找到符合我要求的示例。

基本上,我有一个绑定的 DataGridView 控件(绑定到 DataTable/DataView),并且绑定的 DataTable 有两列:优先级和日期。我想按优先级的日期排序。也就是说,优先级列优先,然后是日期,但两者都可以升序或降序。

因此,例如,我可能有低优先级,提前日期优先 (按优先级 asc,日期 asc 排序),并且通过单击日期列标题,切换到低优先级,较晚日期第一个(按优先级升序排列,日期降序排列)。如果我然后单击优先级,我希望首先具有高优先级,然后是延迟日期(日期列的当前排序顺序 - order by priority desc, date desc),但随后能够单击日期列标题以切换到高优先级,早期日期(按优先级降序排列,日期升序)

理想情况下,我希望对两列的字形进行排序以显示升序或降序。

任何想法或指点都将不胜感激。

这(见下文)似乎非常接近,但字形无法正常工作。

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication4
{
  public partial class Form1 : Form
  {
     DataSet1 dataset;

     public Form1()
     {
        InitializeComponent();

        dataset = new DataSet1(); // two columns: Priority(Int32) and date (DateTime)
        dataset.DataTable1.AddDataTable1Row(1, DateTime.Parse("01-jan-10"));
        dataset.DataTable1.AddDataTable1Row(1, DateTime.Parse("02-jan-10"));
        dataset.DataTable1.AddDataTable1Row(1, DateTime.Parse("03-jan-10"));
        dataset.DataTable1.AddDataTable1Row(2, DateTime.Parse("04-jan-10"));
        dataset.DataTable1.AddDataTable1Row(2, DateTime.Parse("05-jan-10"));
        dataset.DataTable1.AddDataTable1Row(2, DateTime.Parse("06-jan-10"));
        dataset.DataTable1.AddDataTable1Row(3, DateTime.Parse("07-jan-10"));
        dataset.DataTable1.AddDataTable1Row(3, DateTime.Parse("08-jan-10"));
        dataset.DataTable1.AddDataTable1Row(3, DateTime.Parse("09-jan-10"));

        dataGridView1.DataSource = dataset.DataTable1.DefaultView;

        dataGridView1.AllowUserToAddRows = false;

        dataGridView1.Columns[0].SortMode = DataGridViewColumnSortMode.Programmatic;
        dataGridView1.Columns[1].SortMode = DataGridViewColumnSortMode.Programmatic;

        dataGridView1.Columns[0].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
        dataGridView1.Columns[1].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
     }

     private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
     {
        DataGridViewColumn[] column = new[] { dataGridView1.Columns[0], dataGridView1.Columns[1] };

        DataGridViewColumnHeaderCell headerCell = dataGridView1.Columns[e.ColumnIndex].HeaderCell;

        if (headerCell.SortGlyphDirection != SortOrder.Ascending)
           headerCell.SortGlyphDirection = SortOrder.Ascending;
        else
           headerCell.SortGlyphDirection = SortOrder.Descending;

        String sort = column[0].DataPropertyName + " " + fnSortDirection(column[0])
                    + ", "
                    + column[1].DataPropertyName + " " + fnSortDirection(column[1]);
        dataset.DataTable1.DefaultView.Sort = sort;
        this.textBox1.Text = sort;
     }

     private String fnSortDirection(DataGridViewColumn column)
     {
        return column.HeaderCell.SortGlyphDirection != SortOrder.Descending ? "asc" : "desc";
     }
  }
}

【问题讨论】:

  • 关于箭头字形的“工作不正常”是什么意思?我真的不想花时间编写排序代码,但我之前已经让这个看起来正确,现在看起来你已经大部分时间了。
  • 嗯...第一列(优先级)的字形在上下之间切换,但第二列的字形似乎是某种三态,显示为向上,什么都没有, 没有。我猜 DGV 真的不喜欢同时有两个排序字形?我添加了带有“正常”排序的第三列,这看起来很好,但是(前两列上的字形消失了),但是单击第二列的标题然后在第一列上给我一个升序字形。
  • Google for MultisortDataGridView IIRC.

标签: c# sorting datagridview


【解决方案1】:

我第一次阅读这篇文章时,我完全错过了关于同时按多个列排序的部分(我的错,不是你的错;问题非常清楚)。

如果是这种情况,您将不得不自己编写处理此问题的代码。提供的DataGridView 控件默认不支持多列排序。幸运的是,其他人已经为您完成了很多工作来实现这一点。以下是一些示例:

或者,如果您将DataGridView 绑定到数据源,则该数据源可以在多个列上排序,DataGridView 控件将遵循该排序。任何实现IBindingListView 并公开Sort 属性的数据源都适用于多列排序。


但是,无论您选择启用多列排序的路径如何,强制DataGridView 在多列上显示排序箭头字形都不会取得很大成功。这里最简单的解决方案是自定义仅绘制列标题以提供您自己的排序字形。

为此,请将处理程序附加到 DataGridView.CellPainting event 并检查 -1 的 RowIndex(表示列标题)。有一个所有者绘制的列标题here 的完整示例。我强烈建议坚持使用传统的箭头图标,但是一旦你走这条路,选项真的是无限的。您可以使列标题看起来像您想要的任何东西,甚至可以使用不同的图标在排序顺序中指示每列的相对权重。

您还可以选择从DataGridViewColumnHeaderCell 派生一个新类并覆盖其Paint method。这可能是一种更简洁、更面向对象的方式来完成同样的事情。

【讨论】:

  • Cody,非常感谢您抽出宝贵时间提出解决方案 - 非常感谢。我使用了 HeaderCell/OnPaint 方法,但也可能会调查您的其他建议。
【解决方案2】:

好的。

根据上面 Cody 的建议,我现在有了一些似乎可以按预期工作的东西。我对 HeaderCell 进行了子类化并覆盖了 Paint 方法(但通过在 base.Paint 之前设置 SortGlyphDirection 来欺骗),DGV 现在绘制多个排序字形。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication4
{
  public partial class Form1 : Form
  {
     DataSet1 dataset;

     public Form1()
     {
        InitializeComponent();

        dataset = new DataSet1(); // three columns: Priority(Int32), Date (DateTime) and Description(String)
        dataset.DataTable1.AddDataTable1Row(1, DateTime.Parse("01-jan-10"), "this");
        dataset.DataTable1.AddDataTable1Row(1, DateTime.Parse("02-jan-10"), "is");
        dataset.DataTable1.AddDataTable1Row(1, DateTime.Parse("03-jan-10"), "a");
        dataset.DataTable1.AddDataTable1Row(2, DateTime.Parse("04-jan-10"), "sample");
        dataset.DataTable1.AddDataTable1Row(2, DateTime.Parse("05-jan-10"), "of");
        dataset.DataTable1.AddDataTable1Row(2, DateTime.Parse("06-jan-10"), "the");
        dataset.DataTable1.AddDataTable1Row(3, DateTime.Parse("07-jan-10"), "data");
        dataset.DataTable1.AddDataTable1Row(3, DateTime.Parse("08-jan-10"), "in");
        dataset.DataTable1.AddDataTable1Row(3, DateTime.Parse("09-jan-10"), "use");

        dataGridView1.DataSource = dataset.DataTable1.DefaultView;

        dataGridView1.AllowUserToAddRows = false;

        dataGridView1.Columns[0].HeaderCell = new MyDataGridViewColumnHeaderCell();
        dataGridView1.Columns[1].HeaderCell = new MyDataGridViewColumnHeaderCell();

        dataGridView1.Columns[0].SortMode = DataGridViewColumnSortMode.Programmatic;
        dataGridView1.Columns[1].SortMode = DataGridViewColumnSortMode.Programmatic;
     }

     private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
     {
        DataGridViewColumn clickedColumn = dataGridView1.Columns[e.ColumnIndex];

        if (clickedColumn.HeaderCell is MyDataGridViewColumnHeaderCell)
        {
           DoMultiColumnSort();
        }
        else
        {
           dataGridView1.Columns.OfType<DataGridViewColumn>()
                                .Where(column => column.HeaderCell is MyDataGridViewColumnHeaderCell)
                                .ForEach(column => ((MyDataGridViewColumnHeaderCell)column.HeaderCell).SortOrderDirection = SortOrder.None);
        }

        this.textBox1.Text = dataset.DataTable1.DefaultView.Sort;
     }

     private void DoMultiColumnSort()
     {
        var sortClauses = dataGridView1.Columns.OfType<DataGridViewColumn>()
                                               .Where(column => column.HeaderCell is MyDataGridViewColumnHeaderCell)
                                               .Select(column => GetSortClause(column));

        dataset.DataTable1.DefaultView.Sort = String.Join(",", sortClauses);
     }

     private String GetSortClause(DataGridViewColumn column)
     {
        SortOrder direction = column.HeaderCell.SortGlyphDirection;

        if (column.HeaderCell is MyDataGridViewColumnHeaderCell)
        {
           direction = ((MyDataGridViewColumnHeaderCell)column.HeaderCell).SortOrderDirection;
        }

        return column.DataPropertyName + " " + (direction == SortOrder.Descending ? "DESC" : "ASC");
     }
  }

  public partial class MyDataGridViewColumnHeaderCell : DataGridViewColumnHeaderCell
  {
     public SortOrder SortOrderDirection { get; set; } // defaults to zero = SortOrder.None;

     protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle cellBounds, int rowIndex, DataGridViewElementStates dataGridViewElementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
     {
        this.SortGlyphDirection = this.SortOrderDirection;
        base.Paint(graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
     }

     public override object Clone()
     {
        MyDataGridViewColumnHeaderCell result = (MyDataGridViewColumnHeaderCell)base.Clone();
        result.SortOrderDirection = this.SortOrderDirection;
        return result;
     }

     protected override void OnClick(DataGridViewCellEventArgs e)
     {
        this.SortOrderDirection = (this.SortOrderDirection != SortOrder.Ascending) ? SortOrder.Ascending : SortOrder.Descending;
        base.OnClick(e);
     }
  }

  public static partial class Extensions
  {
     public static void ForEach<T>(this IEnumerable<T> value, Action<T> action) { foreach (T item in value) { action(item); } }
  }
}

【讨论】:

  • 我加入 Linq 只是为了好玩,但我不相信它是一个好方法(尤其是“ForEach”扩展)。想法?
【解决方案3】:

DataGridView 绑定到 DataSource (DataView, BindingSource, Table, DataSet+"tablename") 在所有情况下它都引用 DataSource强>数据视图。获取此 DataView 的引用并根据需要设置 Sort(和 Filter):

DataView dv = null;
CurrencyManager cm = (CurrencyManager)(dgv.BindingContext[dgv.DataSource, dgv.DataMember]);

if (cm.List is BindingSource)
{
    // In case of BindingSource it may be chain of BindingSources+relations
    BindingSource bs = (BindingSource)cm.List;
    while (bs.List is BindingSource)
    { bs = bs.List as BindingSource; }

    if (bs.List is DataView)
    { dv = bs.List as DataView; }
}
else if (cm.List is DataView)
{
    // dgv bind to the DataView, Table or DataSet+"tablename"
    dv = cm.List as DataView;
}

if (dv != null)
{
    dv.Sort = "somedate desc, firstname";
    // dv.Filter = "lastname = 'Smith' OR lastname = 'Doe'";

    //  You can Set the Glyphs something like this:
    int somedateColIdx = 5;    // somedate
    int firstnameColIdx = 3;   // firstname
    dgv.Columns[somedateColIdx].HeaderCell.SortGlyphDirection = SortOrder.Descending;
    dgv.Columns[firstnameColIdx].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
}

注意:Sort和Filter中使用的列名对应DataTable中的列名, DataGridView 中的列名是用于绑定的底层数据属性名(类的属性名、DataTables 的列名等)。您可以像这样获取 DataView 中使用的列名:

string colName = dgv.Columns[colIdx].DataPropertyName

取决于您希望如何跟踪已排序的列(colSequence、colName、asc/desc、dgvColIdx),您可以决定如何构建排序和过滤表达式并在 dgv 中设置 SortGlyph(为了简单起见,我做了硬编码)。

【讨论】:

    【解决方案4】:

    我从未在这里回答过问题,因此如果格式不正确,我深表歉意,但我找到了这个问题的答案,这对于未来的访问者来说可能更简单。 (见http://www.pcreview.co.uk/threads/datagridview-glyphs.3145090/

    Dim dictionarySortColumns As New Dictionary(Of String, Integer)
    
    
    Private Sub DataGridViewFileLoader_Sorted(sender As Object, e As EventArgs) Handles DataGridViewFileLoader.Sorted
    
    
        Dim dv As New DataView(dataSetLoadScreener.Tables(0))
        Dim columnHeader As String = DataGridViewFileLoader.SortedColumn.Name
    
        Dim sortDirection As Integer = DataGridViewFileLoader.SortOrder
        Dim sortcode As String = ""
        Dim sortOrder As String = ""
    
        If sortDirection = 1 Then
            sortOrder = "ASC"
        Else
            sortOrder = "DESC"
        End If
    
        If dictionarySortColumns.ContainsKey(columnHeader) Then
            dictionarySortColumns.Remove(columnHeader)
        End If
    
        sortcode = columnHeader + " " + sortOrder
    
        For Each colHeader As String In dictionarySortColumns.Keys
            If dictionarySortColumns(colHeader) = 1 Then
                sortOrder = "ASC"
            Else
                sortOrder = "DESC"
            End If
    
            sortcode = sortcode + "," + colHeader + " " + sortOrder
    
        Next
    
        dictionarySortColumns.Add(columnHeader, sortDirection)
    
        dv.Sort = sortcode
        DataGridViewFileLoader.DataSource = Nothing
        DataGridViewFileLoader.DataSource = dv
    
        formatDataGridViewFileLoader()
    
    End Sub
    
     Private Sub DataGridViewFileLoader_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles DataGridViewFileLoader.CellPainting
        Dim sOrder As System.Windows.Forms.SortOrder
    
        For Each key As String In dictionarySortColumns.Keys
            If dictionarySortColumns(key) = 1 Then
                sOrder = Windows.Forms.SortOrder.Ascending
            Else
                sOrder = Windows.Forms.SortOrder.Descending
            End If
    
            DataGridViewFileLoader.Columns(key).HeaderCell.SortGlyphDirection = sOrder
        Next
    End Sub
    

    【讨论】:

    • 你能解释一下为什么这会帮助 Black Light 解决他们的问题吗?
    • 好吧,我理解 Black Light 的问题是他们希望对绑定到数据视图的列进行主要和次要排序,然后在每个标题上都有字形说明它的方向排序。此代码按照列的排序顺序给出优先级,要排序的最新列具有最高优先级。它还在每列上显示方向字形。
    • 我创建了一个 CellPainting 事件处理程序并设置了 SortGlyphDirection,如示例代码所示。当我按一列排序时,列标题会闪烁。当我按两列或多列排序时,列标题的排序字形仅显示在最后(最右侧)已排序的列上。
    【解决方案5】:

    这是一个类似于 google 电子表格的示例 - 单击每个列标题按该列排序,然后再次单击该列交换搜索方向。此外,它以您单击列的历史记录的相反顺序搜索,即如果您单击列 D,然后单击 B,然后单击 C,然后单击 A,在最后一次单击时,它将按列 A、C、B、D 和在您最近单击该列时搜索的每个方向。

    它依赖于 DataGridView 内置的这些功能:

    • 如果 DataGridView 有一个 DataView 作为其 DataSource,则为该 DataView 设置排序字符串会立即对 DataView 进行排序,并且该排序会立即显示在绑定的 DataGridView 控件中
    • 排序字符串可以包含多列,每一列都可以指示为 ASC 或 DESC 排序
    • 当您设置 DataView 的排序字符串(从而使其排序)时,它所绑定的 DataGridView 将根据您排序中的第一项(名称和方向)自动添加适当的上下字形字符串

    我们从一个排序器对象开始:

    internal class Sorter 
    {
        internal readonly string ColumnName;
        internal bool IsAscending;
    
        internal Sorter(string columnName, bool isAscending)
        {
            this.ColumnName = columnName;
            this.IsAscending = isAscending;
        }
    
        public override bool Equals(object other)
        {  // For equivalence, compare column name only (not object ref or sort order)
            if (other == null) { return false; }
            if (other.GetType() != typeof(Sorter)) { return false; }
            return this.ColumnName == ((Sorter)other).ColumnName;
        }
    
        public override int GetHashCode()
        { // required if we have overridden Equals
            return this.ColumnName.GetHashCode();
        }
    
        public override string ToString()
        {
            return this.ColumnName + (this.IsAscending ? " ASC" : " DESC");
        }
    }
    

    然后将我们的 Sorter 对象收集到一个 Sorters 类中:

    using System.Collections.Generic;
    using System.Linq;
    
    internal class Sorters : List<Sorter>
    {
        internal void BringColumnToFrontOfSortingOrder(Sorter sorter)
        {
            if (this.Contains<Sorter>(sorter))
            {
                this.Remove(sorter); // remove it from where it is
            }
            // put it at the start
            this.Insert(0, sorter);
        }
    
        // Returns a DataView sorting string like "ColName1 ASC, ColName2 DESC" etc
        public override string ToString()
        {
            var s = new System.Text.StringBuilder();
            foreach (Sorter sorter in this)
            {
                if (s.Length > 0) { s.Append(", "); }
                s.Append(sorter.ToString());
            }
            return s.ToString();
        }
    }
    

    我创建了我自己的 MultiSortingDataGridView 类,它派生自 DataGridView,具有一个重写的 Sort 方法,该方法跟踪您单击的列,并根据历史记录对作为我的网格的绑定 DataSource 的 DataView 进行排序您的列排序点击次数:

    using System.Data;
    using System.Windows.Forms;
    using System.ComponentModel;
    
    public class MultiSortingDataGridView : DataGridView // derive from DataGridView and extend it
    {
        private Sorters _sorters = new Sorters(); // keeping track of what column(s) we have searched by
    
        // override the regular search with our super duper multi-column search
        public override void Sort(DataGridViewColumn dataGridViewColumn, ListSortDirection direction)
        {
            Sorter sorter = new Sorter(dataGridViewColumn.Name, direction == ListSortDirection.Ascending);
            this._sorters.BringColumnToFrontOfSortingOrder(sorter);
    
            // Get the data view that is our data source
            DataView vw = (DataView)this.DataSource;
    
            // When you set the Sort property, it causes it to sort, and happily it sets
            // the up/down glyph of the column that corresponds to your first sort-by item.
            vw.Sort = this._sorters.ToString();
        }
    }
    

    然后在我的表单中,我确保使用 MultiSortingDataGridView 控件(如上)而不是常规 DataGridView,并且我将其数据源设置如下:

    DataTable tbl = SomeMethodForGettingMyTabularData(); // instantiate a data table with the data you want to show
    DataView vw = new DataView(tbl); // make a data view from it
    this.dgv.DataSource = vw; // bind the view as data source to my MultiSortingDataGridView control
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多