【问题标题】:How to track sorting in a datagridview如何在 datagridview 中跟踪排序
【发布时间】:2013-02-18 15:44:36
【问题描述】:

我们有一堆 DataGridView 绑定到一个 BindingSource,然后又绑定到一个 SortableBindingList。

由于这个控件使用起来非常痛苦,我正在尝试围绕它编写一个薄包装器,以便大量与网格相关的代码变得更加可重用。通过将其设为通用类 GridWrapper,这还为获取所选项目等常见事物提供了类型安全优势和“无噪音”代码:

/// <summary>
/// Gets the item bound to the selected row if and only if exactly one row is selected; otherwise null.
/// </summary>
public T SelectedItem
{
    get
    {
        var rows = grid.SelectedRows;
        return (rows.Count == 1 ? rows[0].DataBoundItem as T : null);
    }
}

这导致类似的代码

var selectedCustomer = Customers.SelectedItem;

这当然比写和读都容易

Customer selectedCustomer;
if (customersGridView.SelectedRows.Count == 1)
   selectedCustomer = (Customer)customersGridView.SelectedRows[0].DataBoundItem;

并达到同样的效果。无论如何,为什么我要写一个包装器。我的问题与其他事情有关,我现在要解决这个问题。

我希望包装器能够知道网格的默认排序应该是什么,而且还可以跟踪用户执行的排序。这适用于主从网格之类的东西,我想在由于选择主中的另一个项目而反弹时保留详细网格的当前排序。因此,如果我按 Column3 降序对详细信息进行排序,我希望用户代码执行类似

的操作
Detail.Bind(GetDetailData(Master.SelectedItem));

其中 Detail 是详细信息网格的包装器,而 Master 是主网格的包装器。

为此,我从一个简单的类开始存储排序状态:

public class SortInfo
{
    public SortInfo(DataGridViewColumn col)
    {
        this.Column = col;
        this.Direction = ListSortDirection.Ascending;
    }

    public DataGridViewColumn Column;
    public ListSortDirection Direction;
}

包装器将处理程序附加到网格的 Sorted 事件,以跟踪用户排序。我还有一个 Sort 方法可以将状态对象中的排序应用到网格:

void Sort()
{
    if (GetBoundList() != null)
        grid.Sort(sortInfo.Column, sortInfo.Direction);
}

很简单。 (GetBoundList 返回通过 BindingSource 绑定到网格的 SortableBindingList,如果无法这样做,则返回 null。)

现在的问题是:如果我在绑定网格时调用 Sort,我会收到 InvalidOperationException 抱怨网格只能在绑定到 IBindingList 时进行排序!至少可以说这很奇怪,因为在调用 DataGridView.Sort 之前我做的最后一件事是检查情况是否如此。

为了解决上述问题(只要我不理解就很难做任何其他事情!)我尝试将处理程序附加到 DataBindingComplete 并在那里调用 Sort。这会导致另一个问题:当用户尝试对网格进行排序时,SortableBindingList 会重置绑定,从而导致 DataBindingComplete 触发。由于这发生在列表的 ApplySortCore 方法(我认为)返回之前,它也发生在 Sorted 事件之前。因此,当用户尝试按 Column2 排序时,我的包装器会按 Column1 (或任何默认值)排序,从而覆盖用户排序。效果非常奇怪,尽管相同的列标题显示相同的排序箭头,但您可能会看到一些行移动;这是因为项目 正在 被排序,它们只是按照用户的要求首先被排序,然后默认情况下在您真正注意到之前进行排序。如果排序依据的列包含多个相等的值,则生成的顺序可能与原始顺序不同...

在我看来,这个问题与使用包装器本身无关。也就是说,如果我尝试将我的所有代码都放在一个表单中,我会遇到完全相同的问题,而我无法轻松地重用它。

所以问题是是否有人知道如何绕过它。在我写这篇文章时,我想到了这一点:仅在绑定数据时附加 DataBindingComplete 的处理程序,当它触发时,对网格进行排序,然后分离处理程序。在我看来,这实际上应该解决这两个问题。

但是,既然我已经写了很多,我还是会发布这个!如果上面的想法确实有效,我会把它作为答案发布。

同时,这是导致“用户排序被覆盖”表单的包装代码:

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

namespace Snippets.SortableGrid
{
    public class GridWrapper<T> where T: class
    {
        public GridWrapper(DataGridView grid, DataGridViewColumn defaultSortColumn)
        {
            this.sortInfo = new SortInfo(defaultSortColumn);
            this.grid = grid;
            grid.Sorted += new EventHandler(grid_Sorted);
            grid.DataBindingComplete += new DataGridViewBindingCompleteEventHandler(grid_DataBindingComplete);
        }


        public void Bind(IEnumerable<T> data)
        {
            BindingSource bs = grid.DataSource as BindingSource;
            if (bs == null)
                grid.DataSource = bs = new BindingSource();

            bs.DataSource = new SortableBindingList<T>(data);
        }


        /// <summary>
        /// Gets the item bound to the selected row if and only if exactly one row is selected; otherwise null.
        /// </summary>
        public T SelectedItem
        {
            get
            {
                var rows = grid.SelectedRows;
                return (rows.Count == 1 ? rows[0].DataBoundItem as T : null);
            }
        }


        SortInfo sortInfo;
        DataGridView grid;


        ListSortDirection ToListSortDirection(SortOrder order)
        {
            return (order == SortOrder.Descending ? ListSortDirection.Descending : ListSortDirection.Ascending);
        }


        void Sort()
        {
            if (GetBoundList() != null && sortInfo.Column != null)
                grid.Sort(sortInfo.Column, sortInfo.Direction);
        }


        SortableBindingList<T> GetBoundList()
        {
            var bs = grid.DataSource as BindingSource;
            return (bs != null ? (bs.DataSource as SortableBindingList<T>) : null);
        }


        void grid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            Sort();
        }


        void grid_Sorted(object sender, EventArgs e)
        {
            sortInfo.Column = grid.SortedColumn;
            sortInfo.Direction = ToListSortDirection(grid.SortOrder);
        }
    }


    public class SortInfo
    {
        public SortInfo(DataGridViewColumn col)
        {
            this.Column = col;
            this.Direction = ListSortDirection.Ascending;
        }

        public DataGridViewColumn Column;
        public ListSortDirection Direction;
    }
}

【问题讨论】:

  • 我希望您的 SortableBindingList 确实实现了IBindingList

标签: .net winforms datagridview


【解决方案1】:

我并没有完全使用这个代码很长时间,但这个想法似乎确实有效!

将绑定更改为

public void Bind(IEnumerable<T> data)
{
    BindingSource bs = grid.DataSource as BindingSource;
    if (bs == null)
        grid.DataSource = bs = new BindingSource();

    grid.DataBindingComplete += new DataGridViewBindingCompleteEventHandler(grid_DataBindingComplete);
    bs.DataSource = new SortableBindingList<T>(data);
}

和处理程序

void grid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
    Sort();
    grid.DataBindingComplete -= grid_DataBindingComplete;
}

我现在可以重新绑定“网格”(即使用一些新数据调用 GridWrapper.Bind),并且网格确实“记住”了它的排序状态。这就是我想要开始的全部内容。

--

我真的不明白为什么在 2013 年有必要为这种事情在 all 编写任何代码。我们得到的控件应该对像这个内置的常见和有用的东西有更好的支持。

虽然我在咆哮,但我也非常怀疑事件始终是吸引事物的最佳方式的整个想法。在一个人们尝试做 MVC 或 MVP 或类似的世界里,为什么不给我们一些更紧密一致的东西——更类似于这个包装器的东西——开箱即用?如果控件允许您至少在某些方面将它们基本上视为数据结构,那么实现视图会容易得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-07
    • 1970-01-01
    相关资源
    最近更新 更多