0

我们有一堆绑定到 BindingSource 的 DataGridView,然后又绑定到了 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;
    }
}
4

1 回答 1

0

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

更改绑定到

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 年有必要为这种事情编写任何代码。我们得到的控件应该对像这个内置的常见和有用的东西有更好的支持。

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

于 2013-02-18T15:54:06.397 回答