【问题标题】:How to enable DataGridView sorting when user clicks on the column header?用户单击列标题时如何启用 DataGridView 排序?
【发布时间】:2011-04-05 14:01:39
【问题描述】:

我的表单上有一个 datagridview,我用这个填充它:

dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })
                                   .OrderBy(s => s.Apellidos)
                                   .ToList();

现在,我使用 s.Apellidos 作为默认排序,但我还希望允许用户在单击列标题时进行排序。

这种排序方式不会以任何方式修改数据,它只是客户端的一个好处,可以让他们在用眼睛扫描屏幕时更轻松地搜索信息。

感谢您的建议。

【问题讨论】:

标签: c# winforms sorting datagridview


【解决方案1】:

将所有列(可由用户排序)的 SortMode 属性设置为自动

dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })
                                   .OrderBy(s => s.Apellidos)
                                   .ToList();

    foreach(DataGridViewColumn column in dataGridView1.Columns)
    {
    
        column.SortMode = DataGridViewColumnSortMode.Automatic;
    }

编辑:由于您的 datagridview 与 linq 查询绑定,因此不会对其进行排序。所以请仔细阅读这个[404 dead link, see next section],它解释了如何创建一个可排序的绑定列表,然后将其作为数据源提供给 datagridview。

从死链接中恢复的代码

上面的链接是 404-dead。我从页面的 Internet Wayback Machine archive 中恢复了代码。

public Form1()
{
    InitializeComponent();

    SortableBindingList<person> persons = new SortableBindingList<person>();
    persons.Add(new Person(1, "timvw", new DateTime(1980, 04, 30)));
    persons.Add(new Person(2, "John Doe", DateTime.Now));

    this.dataGridView1.AutoGenerateColumns = false;
    this.ColumnId.DataPropertyName = "Id";
    this.ColumnName.DataPropertyName = "Name";
    this.ColumnBirthday.DataPropertyName = "Birthday";
    this.dataGridView1.DataSource = persons;
}

【讨论】:

  • 由于我希望所有列都可排序,有没有办法以编程方式遍历每一列并设置此属性?
  • @Niraj:我收到编译器错误。我尝试将您的答案更改为 column.Name 但它似乎并没有以任何方式修改排序。
  • @Sergio:所以如果我理解正确,现在您不会收到任何错误,但列仍然无法排序。
  • @Niraj:正确。我单击列标题,行不自行排序。
  • 顺便说一句,您的 foreach 可能更简单:foreach(DataGridViewColumn column in dataGridView1.Columns) column.SortMode = DataGridViewColumnSortMode.Automatic;
【解决方案2】:

按照 Niraj 的建议,使用 SortableBindingList。我在 DataGridView 中非常成功地使用了它。

这是我使用的更新代码的链接 - Presenting the SortableBindingList - Take Two - archive

只需将这两个源文件添加到您的项目中,您就可以开始工作了。

来源在SortableBindingList.zip - 404 dead link

【讨论】:

  • 此解决方案不需要 LINQ 并在 .net 2.0 中工作,并且在 BindingList 中可以找到其他有用的功能,例如更改通知。感谢分享。
  • 源不存在了,有什么办法更新吗?
  • 正是我想要的。谢谢。
  • @Arend 我添加了指向死链接的回路机存档的链接
【解决方案3】:

另一种方法是使用“System.Linq.Dynamic”库。您可以从Nuget 获取此库。不需要任何自定义实现或可排序列表:)

using System.Linq.Dynamic;
private bool sortAscending = false;

private void dataGridView_ColumnHeaderMouseClick ( object sender, DataGridViewCellMouseEventArgs e )
{
    if ( sortAscending )
        dataGridView.DataSource = list.OrderBy ( dataGridView.Columns [ e.ColumnIndex ].DataPropertyName ).ToList ( );
    else
        dataGridView.DataSource = list.OrderBy ( dataGridView.Columns [ e.ColumnIndex ].DataPropertyName ).Reverse ( ).ToList ( );
    sortAscending = !sortAscending;
}

【讨论】:

  • 工作就像一个魅力,不敢相信还没有人给你积极的反馈呢
  • else 的情况下使用OrderByDescending 会更好。
  • 效果很好!不要忘记添加dataGridView.ColumnHeaderMouseClick += dataGridView_ColumnHeaderMouseClick; 并为 BindingList 实现此功能,每次数据进入时都会更新...您必须修改代码...dataGridView.DataSource = new BindingList&lt;Data&gt;(dataList.OrderBy(dataGridView.Columns[e.ColumnIndex].DataPropertyName).ToList());
【解决方案4】:

您不需要创建绑定数据源。如果您想对所有列应用排序,这是我的一个更通用的解决方案;

private int _previousIndex;
private bool _sortDirection;

private void gridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.ColumnIndex == _previousIndex)
        _sortDirection ^= true; // toggle direction

    gridView.DataSource = SortData(
        (List<MainGridViewModel>)gridReview.DataSource, gridReview.Columns[e.ColumnIndex].Name, _sortDirection);

    _previousIndex = e.ColumnIndex;
}

public List<MainGridViewModel> SortData(List<MainGridViewModel> list, string column, bool ascending)
{
    return ascending ? 
        list.OrderBy(_ => _.GetType().GetProperty(column).GetValue(_)).ToList() :
        list.OrderByDescending(_ => _.GetType().GetProperty(column).GetValue(_)).ToList();
}

确保您的数据网格订阅了事件ColumnHeaderMouseClick。当用户单击该列时,它将按降序排序。如果再次点击同一列标题,则按升序排序。

【讨论】:

  • 不错的解决方案。谢谢。
【解决方案5】:

您可以像这样使用 DataGridViewColoumnHeaderMouseClick 事件:

Private string order = String.Empty;
private void dgvDepartment_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    if (order == "d")
{
        order = "a";                
dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })   .OrderBy(s => s.Apellidos).ToList();
    }
    else
    {
        order = "d";
        dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth }.OrderByDescending(s => s.Apellidos)  .ToList()
    }
}

【讨论】:

  • 我想使用这个想法,但我如何管理点击了哪个列标题?按该特定列名对datagridview 顺序进行排序?谢谢
  • @mr.dev.eloper - DataGridViewCellMouseEventArgs 对象包含“ColumnIndex”属性。
【解决方案6】:

您的数据网格首先需要绑定到一个可排序的列表。

创建此事件处理程序:

    void MakeColumnsSortable_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
    {
        //Add this as an event on DataBindingComplete
        DataGridView dataGridView = sender as DataGridView;
        if (dataGridView == null)
        {
            var ex = new InvalidOperationException("This event is for a DataGridView type senders only.");
            ex.Data.Add("Sender type", sender.GetType().Name);
            throw ex;
        }

        foreach (DataGridViewColumn column in dataGridView.Columns)
            column.SortMode = DataGridViewColumnSortMode.Automatic;
    }

并像这样初始化每个 datragrids 的事件:

        dataGridView1.DataBindingComplete += MakeColumnsSortable_DataBindingComplete;

【讨论】:

  • 我喜欢使用 DataBindingComplete 事件的想法,但这并不能解决没有可排序数据集的问题。我已经对其进行了编辑以使其更具可重用性
【解决方案7】:


使用实体框架(在本例中为版本 6)时有一个非常简单的解决方案。我不确定,但似乎ObservableCollectionExtensions.ToBindingList&lt;T&gt; 方法返回了 sortable 绑定列表的实现。我还没有找到源代码来证实这个假设,但是从这个方法返回的对象可以很好地与DataGridView 一起工作,尤其是在通过单击其标题对列进行排序时。

代码非常简单,只依赖.net和实体框架类:

using System.Data.Entity;

IEnumerable<Item> items = MethodCreatingItems();

var observableItems = new System.Collections.ObjectModel.ObservableCollection<Item>(items);
System.ComponentModel.BindingList<Item> source = observableItems.ToBindingList();

MyDataGridView.DataSource = source;

【讨论】:

    【解决方案8】:

    KISS : 保持简单,笨蛋

    方式 A: 在喜欢使用 DataBindingsorting 时实现自己的 SortableBindingList 类。

    方式 B: 使用 List 排序也适用,但不适用于 DataBinding

    【讨论】:

      【解决方案9】:

      我建议使用 DataTable.DefaultView 作为数据源。然后是下面的行。

      foreach (DataGridViewColumn column in gridview.Columns)
          {
             column.SortMode = DataGridViewColumnSortMode.Automatic;
          }
      

      之后,gridview 本身将管理排序(支持升序或降序。)

      【讨论】:

      • 只是传递一个 DataTable 对我有用。无需摆弄添加模块等。非常感谢!
      【解决方案10】:

      如果您收到类似的错误消息

      “System.NullReferenceException”类型的未处理异常 发生在 System.Windows.Forms.dll 中

      如果您使用 SortableBindingList,您的代码可能会在 DataGridView 行上使用一些循环,并且还会尝试访问空的最后一行! (BindingSource = null)

      如果不需要让用户直接在DataGridView中添加新行,这行代码轻松解决问题:

      InitializeComponent();
      m_dataGridView.AllowUserToAddRows = false; // after components initialized
      ...
      

      【讨论】:

        【解决方案11】:
        1. 创建一个包含您需要的所有属性的类,并在构造函数中填充它们

          class Student
          {
              int _StudentId;
              public int StudentId {get;}
              string _Name;
              public string Name {get;}
              ...
          
              public Student(int studentId, string name ...)
              { _StudentId = studentId; _Name = name; ... }
          }
          
        2. 创建一个 IComparer 类,以便能够排序

          class StudentSorter : IComparer<Student>
          {
              public enum SField {StudentId, Name ... }
              SField _sField; SortOrder _sortOrder;
          
              public StudentSorder(SField field, SortOrder order)
              { _sField = field; _sortOrder = order;}
          
              public int Compare(Student x, Student y)
              {
                  if (_SortOrder == SortOrder.Descending)
                  {
                      Student tmp = x;
                      x = y;
                      y = tmp;
                  }
          
                  if (x == null || y == null)
                      return 0;
          
                  int result = 0;
                  switch (_sField)
                  {
                      case SField.StudentId:
                          result = x.StudentId.CompareTo(y.StudentId);
                          break;
                      case SField.Name:
                          result = x.Name.CompareTo(y.Name);
                          break;
                          ...
                  }
          
                  return result;
              }
          }
          
        3. 在包含数据网格的表单中添加

          ListDictionary sortOrderLD = new ListDictionary(); //if less than 10 columns
          private SortOrder SetOrderDirection(string column)
          {
              if (sortOrderLD.Contains(column))
              {
                  sortOrderLD[column] = (SortOrder)sortOrderLD[column] == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
              }
              else
              {
                  sortOrderLD.Add(column, SortOrder.Ascending);
              }
          
              return (SortOrder)sortOrderLD[column];
          }
          
        4. 在 datagridview_ColumnHeaderMouseClick 事件处理程序中执行类似的操作

          private void dgv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
          {
              StudentSorter sorter = null;
              string column = dGV.Columns[e.ColumnIndex].DataPropertyName; //Use column name if you set it
              if (column == "StudentId")
              {
                  sorter = new StudentSorter(StudentSorter.SField.StudentId, SetOrderDirection(column));
              }
              else if (column == "Name")
              {
                  sorter = new StudentSorter(StudentSorter.SField.Name, SetOrderDirection(column));
              }
          
              ...
          
              List<Student> lstFD = datagridview.DataSource as List<Student>;
              lstFD.Sort(sorter);
              datagridview.DataSource = lstFD;
              datagridview.Refresh();
          }
          

        希望对你有帮助

        【讨论】:

          【解决方案12】:

          将此行放入您的 Windows 窗体中(在加载时或在“binddata”等公共方法中更好):

          //
          // bind the data and make the grid sortable 
          //
          this.datagridview1.MakeSortable( myenumerablecollection ); 
          

          将此代码放入名为 DataGridViewExtensions.cs(或类似文件)的文件中

          // MakeSortable extension. 
          // this will make any enumerable collection sortable on a datagrid view.  
          
          //
          // BEGIN MAKESORTABLE - Mark A. Lloyd
          //
          // Enables sort on all cols of a DatagridView 
          
          //
          
          
          
              using System;
              using System.Collections.Generic;
              using System.Linq;
              using System.Threading.Tasks;
              using System.Windows.Forms;
          
              public static class DataGridViewExtensions
              {
              public static void MakeSortable<T>(
                  this DataGridView dataGridView, 
                  IEnumerable<T> dataSource,
                  SortOrder defaultSort = SortOrder.Ascending, 
                  SortOrder initialSort = SortOrder.None)
              {
                  var sortProviderDictionary = new Dictionary<int, Func<SortOrder, IEnumerable<T>>>();
                  var previousSortOrderDictionary = new Dictionary<int, SortOrder>();
                  var itemType = typeof(T);
                  dataGridView.DataSource = dataSource;
                  foreach (DataGridViewColumn c in dataGridView.Columns)
                  {
                      object Provider(T info) => itemType.GetProperty(c.Name)?.GetValue(info);
                      sortProviderDictionary[c.Index] = so => so != defaultSort ? 
                          dataSource.OrderByDescending<T, object>(Provider) : 
                          dataSource.OrderBy<T,object>(Provider);
                      previousSortOrderDictionary[c.Index] = initialSort;
                  }
          
                  async Task DoSort(int index)
                  {
          
                      switch (previousSortOrderDictionary[index])
                      {
                          case SortOrder.Ascending:
                              previousSortOrderDictionary[index] = SortOrder.Descending;
                              break;
                          case SortOrder.None:
                          case SortOrder.Descending:
                              previousSortOrderDictionary[index] = SortOrder.Ascending;
                              break;
                          default:
                              throw new ArgumentOutOfRangeException();
                      }
          
                      IEnumerable<T> sorted = null;
                      dataGridView.Cursor = Cursors.WaitCursor;
                      dataGridView.Enabled = false;
                      await Task.Run(() => sorted = sortProviderDictionary[index](previousSortOrderDictionary[index]).ToList());
                      dataGridView.DataSource = sorted;
                      dataGridView.Enabled = true;
                      dataGridView.Cursor = Cursors.Default;
          
                  }
          
                  dataGridView.ColumnHeaderMouseClick+= (object sender, DataGridViewCellMouseEventArgs e) => DoSort(index: e.ColumnIndex);
              }
          }
          

          【讨论】:

            【解决方案13】:

            以防万一有人还在寻找它,我在 VS 2008 C# 上做了。

            在事件 ColumnHeaderMouseClick 上,为 gridview 添加数据绑定,并像参数一样发送 order by 字段。可以通过如下方式获取点击的字段:

            dgView.Columns[e.ColumnIndex].Name
            

            在我的例子中,标题的名称类似于视图字段名称。

            【讨论】:

              【解决方案14】:

              我有一个 BindingList 对象作为数据源绑定到 dataGridView。

              BindingList x1;
              x1 = new BindingList<sourceObject>();
              BindingSource bsx1 = new BindingSource();
              bsx1.DataSource = x1;
              dataGridView1.DataSource = bsx1;
              

              当我单击列标题时,没有进行排序。 我使用了 Tom Bushell 提供的 SortableBindingList 答案。 在我的项目中包含了两个源文件

              1. SortableBindingList.cs
              2. PropertyComparer.cs

              然后对我的代码进行此更改:

              Be.Timvw.Framework.ComponentModel.SortableBindingList x1;                       // 1
              x1 = new Be.Timvw.Framework.ComponentModel.SortableBindingList<sourceObject>(); // 2
              BindingSource bsx1 = new BindingSource();
              bsx1.DataSource = x1;
              dataGridView1.DataSource = bsx1;
              

              在这些更改之后,我对我的程序执行了构建。我现在可以通过单击列标题进行排序。只有两行需要更改,它们在上面的代码 sn-p 中通过尾随 cmets 突出显示。

              【讨论】:

                【解决方案15】:

                就我而言,问题在于我将我的DataSource 设置为object,这就是它没有被排序的原因。从object 更改为DataTable 后,它运行良好,无需任何代码补充。

                【讨论】:

                  【解决方案16】:

                  如果使用DataTabledgv.DataSource = (DataTable)table

                  您可以为包含IComparable 接口的对象自动启用排序。创建 DataTable 后,添加列时,请务必将类型也设置为至少 objecttable.Columns.Add("ColumnName", typeof(object))

                  否则,如果你没有专门给它一个类型,它会将对象转换为一个字符串。

                  我花了相当多的时间创建一个dgv_ColumnHeaderMouseClick() 事件,因为它没有正确排序DataGridView,然后发现您需要做的就是指定列名的类型,然后排序正确。之前没有正确排序的原因是因为没有指定DataTable列的类型,它将对象转换为字符串。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-12-17
                    • 1970-01-01
                    • 2016-10-26
                    • 2013-07-06
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多