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;
}
ラッパーは、ユーザーの並べ替えを追跡するために、グリッドの並べ替えイベントにハンドラーをアタッチします。状態オブジェクトの並べ替えをグリッドに適用するSortメソッドもあります。
void Sort()
{
if (GetBoundList() != null)
grid.Sort(sortInfo.Column, sortInfo.Direction);
}
ものすごく単純。(GetBoundListは、BindingSourceを介してグリッドにバインドされたSortableBindingListを返します。それが不可能な場合は、nullを返します。)
ここで問題が発生します。グリッドがバインドされているときにSortを呼び出すと、IBindingListにバインドされている場合にのみグリッドを並べ替えることができるというInvalidOperationExceptionが発生します。控えめに言っても、これは奇妙なことです。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;
}
}