我们有一堆绑定到 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;
}
}