Michael Weinhardt
www.mikedub.net (英語)
January 5, 2003
日本語版最終更新日 : 2004 年 3 月 17 日
概要 : 今回も、新しい "Whidbey" Windows フォームの GridView コントロールに関する調査を続けます。 特に、移動、編集、検証、エラー処理などの一般的な操作を処理するためのさまざまなイベントに注目します。
サンプル ファイル (winforms02172004_Sample.msi) のダウンロード
シドニー上空の花火
昨夜は大晦日でした。新年がみなさんにとって幸せで安全で平和な年になることをお祈りしたいと思います。 私はオーストラリアのシドニーに住んでいます。シドニーでは、自慢の華麗な花火が、午前 0 時ちょうどにシドニー ハーバー ブリッジから打ち上げられます。 大晦日は、世界中の多くの都市で、1 年中で最も盛大なイベントの 1 つとなっています。 GridView コントロールもすぐに、世界中のあらゆる都市で称賛されるようになることでしょう。GridView コントロールには、"Whidbey" の Technology Preview リリースの時点で 130 を超えるイベントが用意されているので、大晦日のように 1 つのイベントとして称賛されるだけではありません。 前回のコラムでは、OnCellFormatting と OnCellPainting を使用して、カスタム セルのスタイル設定ロジックと塗りつぶしロジックを実装しました。 今回のコラムでは、移動、編集、検証、およびエラー処理用のイベントについて調査を続けていきます。
サンプル
このようなイベントを例示するために、図 1 に示すような製品エディタをビルドしました。
図 1. 製品エディタ
製品エディタは、単純な Windows フォーム アプリケーションです。このエディタでは、Northwind データベースの Products テーブルに GridView が連結され (この処理方法の詳細については、先月のコラムを参照してください)、以下の目的でさまざまなイベントが使用されます。
- ユーザーに位置による場所情報とコンテキストによる場所情報を提供します。
- 値ごとの確認を行うために編集処理にフックします。
- クライアント側の型および範囲の検証エラーを中継します。
- カスタム検証をさらに行ってクライアント側の検証を補足します。
移動
ユーザーは、図 1 のようなフォームが表示されたとき、以下のいずれかの操作を実行することになります。
- データをざっと見渡して特定のセルや値を探します。
- 並べ替えと検索の機能を使用して特定のセルや値を探します。
- GridView の最後までスクロールし、新しい行を追加します。
- 削除する特定の行を選択します。
位置による情報の表示
上記の操作はすべて、1 つのセルから別のセルへの移動が必要であり、行間の移動が必要になる場合もあります。 場合によっては、実際のセルの位置を判断することが困難なことがあります。特に、大量のデータが表示されているときは困難になります。 Microsoft Excel や Microsoft Word などのアプリケーションでは、ステータス バーなどのコントロールを使用して、この種の情報がユーザーに表示されることがあります。 この情報を報告するには、ユーザーが移動した時点を通知するイベント、および現在の行と列の位置を示す情報が必要になります。 GridView の CellEnter イベントは、次のように、このようなイベントと情報をどちらも提供します。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.CellEnter +=
new System.Windows.Forms.GridViewCellEventHandler(
this.productsGridView_CellEnter);
...
private void customersGridView_CellEnter(
object sender,
System.Windows.Forms.GridViewCellEventArgs e) {
// 0 から始まるインデックスに 1 を追加して行と列の位置を表示します
int row = e.RowIndex + 1;
int column = e.ColumnIndex + 1;
this.pnlPosition.Text = string.Format("Row {0}, Col {1}", row, column);
}
...
}
この例では、GridViewCellEventArgs によって提供される RowIndex プロパティと ColumnIndex プロパティを使用しています。 また、次のように GridView の SelectedCells プロパティを問い合わせて、行と列の位置を確認することもできます。
int columnIndex = this.productsGridView.SelectedCells[0].ColumnIndex;
int rowIndex = this.productsGridView.SelectedCells[0].RowIndex;
ただし、位置情報が必要な場合は、SelectedCells ではなく GridViewCellEventArgs を使用する必要があります。 GridView に最初からデータが設定されているときは、SelectedCells は空になります。このことは、上記のコードによって ArgumentOutOfRangeException がスローされる原因になります。一方、GridViewCellEventArgs は、セルが選択されていない場合でも、常に有効な行インデックスと列インデックスを移動イベントに提供します。 さらに、RowIndex と ColumnIndex は、GridView のオブジェクト モデルに直接マップされるので、それらプロパティを使用して、"ProductId " と列名を示す GridView の位置をさらに判読しやすく表示できます。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.CellEnter +=
new System.Windows.Forms.GridViewCellEventHandler(
this.productsGridView_CellEnter);
...
private void customersGridView_CellEnter(
object sender,
System.Windows.Forms.GridViewCellEventArgs e) {
// 0 から始まるインデックスを補正するために 1 を追加して位置を表示します
int row = e.RowIndex + 1;
int column = e.ColumnIndex + 1;
this.pnlPosition.Text = string.Format("Row {0}, Col {1}", row, column);
// 人間が判読可能な形式で現在位置を表示します
GridViewRow gridRow = this.productsGridView.Rows[e.RowIndex];
GridViewColumn gridColumn =
this.productsGridView.Columns[e.ColumnIndex];
string productId = gridRow.Cells["ProductID"].Value.ToString();
string columnName = gridColumn.Name;
this.pnlHuman.Text = string.Format("Product {0}: {1}", productId, columnName);
}
...
}
コンテキストによる情報の表示
ユーザーが位置情報を入手することが困難であるように、セルが読み取り専用であるかどうかなど、現在のセルに関する特定の詳細も一見しただけではわかりにくいことがあります。 サンプルでは、ProductID は AutoIncrement 列なので読み取り専用です。 したがって、ProductID の BackColor を LightSteelBlue に設定して、ProductID が読み取り専用であることを強調表示しようとしました。 ただし、場合によっては、"読み取り専用" などと単語を表示する方がよく目立ち、グリッド位置のようにステータス バーに表示する必要がある場合もあります。 この操作を行うには、現在のセルが読み取り専用であるかどうかをプログラムで判断する必要があります。 幸いなことに、RowIndex と ColumnIndex は GridView のオブジェクト モデルに直接マップされるので、次のように、選択した列に移動してこの情報を検索できます。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.CellEnter +=
new System.Windows.Forms.GridViewCellEventHandler(
this.productsGridView_CellEnter);
...
private void customersGridView_CellEnter(
object sender,
System.Windows.Forms.GridViewCellEventArgs e) {
// 0 から始まるインデックスを補正するために 1 を追加して位置を表示します
int row = e.RowIndex + 1;
int column = e.ColumnIndex + 1;
this.pnlPosition.Text = string.Format("Row {0}, Col {1}", row, column);
// 人間が判読可能な形式で現在位置を表示します
GridViewRow gridRow = this.productsGridView.Rows[e.RowIndex];
GridViewColumn gridColumn =
this.productsGridView.Columns[e.ColumnIndex];
string productId = gridRow.Cells["ProductID"].Value.ToString();
string columnName = gridColumn.Name;
this.pnlHuman.Text = string.Format("Product {0}: {1}", productId, columnName);
// 現在の列が読み取り専用であるかどうかを表示します
if( this.productsGridView.Columns[e.ColumnIndex].ReadOnly == true ) {
this.pnlReadOnly.Text = "Read-Only";
}
else {
this.pnlReadOnly.Text = "Editable";
}
}
...
}
図 2 に、現在調査中の CellEnter イベントの結果を示します。
図 2. 行と列の位置、および編集可能なセルの表示
興味深いことに、RowEnter と RowLeave の両方が、初めにより明白であった GridViewRowEventArgs ではなく、GridViewCellEventArgs を引数として受け取ります。 ただし、このことは、行の入力がセルの入力を示すので理にかなっています。 GridViewCellEventArgs にアクセスできることは、コードが RowEnter と RowLeave にしか適合しない場合に、CellEnter と CellLeave によりセルの位置を追跡することを避けるのに役立つので、柔軟性に優れた機能です。
移動イベント
定期的に使用する可能性がある移動イベントがいくつかあります。次の表で、そのような移動イベントの概要を説明します。
| イベント | 説明 |
| RowLeave | 以下の場合に発生します。
- 新しいセルが新しい行で選択されたとき。
- GridView コントロールがフォーカスを失ったとき。
|
| CellLeave | 以前選択したセルの選択が解除されるたびに発生します。セルの選択は、以下の場合に解除されます。
- 新しいセルを選択したとき。
- 新しい行を選択したとき。
- GridView コントロールがフォーカスを失ったとき。
|
| CellEnter | 以下の場合、必ず発生します。
- 新しいセルを選択したとき。
- 新しい行を選択したとき。
- GridView コントロールがフォーカスを取得したとき。
|
| RowEnter | 新しい行が選択されると発生します。新しい行は、以下の場合に選択されます。
- GridView にデータを読み込んだとき。
- 別の行のセルを選択したとき。
- 別のコントロールにフォーカスがあるときに、GridView コントロールのセルを選択したとき。
|
| CellClick | ユーザーがマウスをクリックして 1 つのセルが選択されたときに発生します。 |
実行している移動の種類に応じて、さまざまな方法で、行やセルのさまざまな移動イベントを発生させることができます。 次の表で、GridView 内で移動するためのいくつかの一般的なシナリオの概要について説明します。
| シナリオ | 発生するイベント |
| GridView へのデータの読み込み |
- RowEnter
- CellEnter
|
| 同じ行の新しいセルを選択 |
- CellLeave
- CellEnter
|
| 別の行の新しいセルを選択 |
- CellLeave
- RowLeave
- RowEnter
- CellEnter
|
ユーザーが GridView コントロールに移動したり、GridView コントロールから離れたときに、移動イベントがどのように発生するかについてはあまり明らかではありません。 次の表で、このようなシナリオを明らかにします。
| シナリオ | 発生するイベント |
| フォーム上の別のコントロールを選択 |
- CellLeave
- RowLeave
- Leave
|
| 別のコントロールで現在選択されているセルを再度選択 |
- Enter
- RowEnter
- CellEnter
|
| 別のコントロールで現在選択されているセルと同じ行の新しいセルを選択 |
- Enter
- RowEnter
- CellEnter
- CellLeave
- CellEnter
|
| 別のコントロールで現在選択されているセルとは異なる、別の行の新しいセルを選択 |
- Enter
- RowEnter
- CellEnter
- CellLeave
- RowLeave
- RowEnter
- CellEnter
|
お気付きのように、コントロールがフォーカスを受け取ったり、フォーカスを失ったりしたときに、GridView の Enter イベントと Leave イベントがそれぞれ有効になり、発生します。 特に、別のコントロールにフォーカスがあるときに、GridView のセルが選択されると、GridView は、新しく選択されたセルのイベントを発生させる前に、現在選択されているセルのイベントを発生させます。 したがって、このようなイベントの暗黙の範囲内でのみ処理するイベントにコードを配置するようにします。 それ以外の場合、ロジックが予想よりも多くの回数実行されたり、予想とは異なる順番で実行されたりすることがあります。また、ロジックでは予定していない副作用が生じる可能性があります。 幸いなことに、ロジックを適切な場所に整然とパッケージできるだけの十分なイベントがあります。
編集
当然のことながら、移動は、編集可能なグリッドでのウィンドウ ショッピングにすぎません。 幼少時代、両親と一緒に遠くから見ただけでも高価だとわかる商品を置いている店に入るときはいつも、私は両親から「見るだけで、触ってはいけないよ」と言われていました。それは、手当たり次第に何でも壊す癖が私にあったからです。 しかし、最近は、GridView の方が当時の私の両親よりも寛容です。
行の編集
たとえば、GridView から行を削除する場合、UserDeletingRow イベントを処理してアクションをキャンセルし、間違った削除の可能性を回避できます。これは、まさに、上記のような高級品を取り扱う店が導入を願った機能です。次のコードでは、標準のアプローチを使用してこの操作の実行方法を示します。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.UserDeletingRow +=
new System.Windows.Forms.GridViewRowCancelEventHandler(
this.productsGridView_UserDeletingRow);
...
private void suppliersGridView_UserDeletingRow(
object sender,
System.Windows.Forms.GridViewRowCancelEventArgs e) {
// ユーザーに、行が削除されることを確認するよう依頼します
string productId = e.Row.Cells["ProductId"].FormattedValue.ToString();
string productName = e.Row.Cells["ProductName"].FormattedValue.ToString();
string message = string.Format(
"Are you sure you want to delete {0} [{1}]?",
productName,
productId);
DialogResult result = MessageBox.Show(
message,
"Delete?",
MessageBoxButtons.OKCancel);
if( result == DialogResult.Cancel ) {
e.Cancel = true;
this.pnlMain.Text = string.Format(
"Cancelled deletion of {0} [{1}].",
productName,
productId);
}
}
...
}
UserDeletingRow は、次のように、GridViewRowCancelEventArgs 引数として渡されます。
public class GridViewRowCancelEventArgs : System.ComponentModel.CancelEventArgs {
// コンストラクタ
public GridViewRowCancelEventArgs ( System.Windows.Forms.GridViewRow gridViewRow );
// 削除する行にアクセスします
public System.Windows.Forms.GridViewRow Row { get; }
}
GridViewRowCancelEventArgs は、Row プロパティからさまざまな行データを収集するだけでなく、継承された Cancel プロパティを True に設定することで、削除処理をキャンセルするメカニズムを提供します。 ユーザーが削除処理の続行を希望する場合、次のように、完了時に UserDeletedRow イベントから適切なメッセージを表示できます。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.UserDeletedRow +=
new System.Windows.Forms.GridViewRowEventHandler(
this.productsGridView_UserDeletedRow);
...
private void suppliersGridView_UserDeletedRow(
object sender,
System.Windows.Forms.GridViewRowEventArgs e) {
this.pnlMain.Text = string.Format("Row deleted.");
}
...
}
UserDeletedRow には、Row プロパティによって処理される行へのアクセス許可を提供する GridViewRowEventArgs 引数が渡されます。 また、その他の重要な行の編集イベントである UserAddedRow にも、イベントの発生時に GridViewRowEventArgs が渡されます。イベントは、ユーザーが新しい行の既定値を変更したときに発生します。
セルの編集
行に役立つ機能を見ておくことは役に立ちます。 GridView には、CellBeginEdit や CellEndEdit などのセルの編集イベントも実装されています。 これらのイベントが発生する前に、特定の条件を満たしている必要があります。 特に、選択したセルは編集可能である必要があります。これは、手動または自動的に設定できます。 既定の GridView アプローチは、手動のアプローチです。このアプローチでは、ユーザーが F2 キーを押すか、編集が必要なそれぞれすべてのセルをダブルクリックする必要があります。 自動的なアプローチは、GridView の
EditCellOnEnter プロパティを True に設定することで有効になります。このアプローチを使用すると、セルが読み取り専用である場合を除いて、ユーザーが介入しなくても入力時に各セルが編集モードに切り替わります。 これらの 2 つのアプローチのどちらを使用しても、セルの選択後に CellBeginEdit が発生します。 CellBeginEdit には、次のように GridViewCellCancelEventArgs が渡されます。
public class GridViewCellCancelEventArgs : System.ComponentModel.CancelEventArgs {
public System.Int32 ColumnIndex { get; }
public System.Int32 RowIndex { get; }
}
以前確認したように、ColumnIndex および RowIndex はコンテキスト内のセルを参照します。 また、以前のように Cancel プロパティを使用して、編集できるようにしたり編集されないようにしたりすることもできます。 このプロパティは、場合によって判断する必要があるときに役立ちます。 Products テーブルには、Discontinued フィールドを使用して、製品をまだ入手できるかどうかが記録されます。 Discontinued が True の場合、同じ行の UnitsOnOrder セルを更新できるようにしても意味がありません。 また、当然のことながら、Discontinued はある行から次の行に変更できます。 このことに対応している CellBeginEdit は、行ごとに変更できます。 BeginCellEdit を使用すると、このビジネス ロジックを利用でき、次のように、Discontinued セルの値に基づいて編集を許可または拒否できます。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.CellBeginEdit +=
new System.Windows.Forms.GridViewCellCancelEventHandler(
this.productsGridView_CellBeginEdit);
...
private void productsGridView_CellBeginEdit(
object sender,
System.Windows.Forms.GridViewCellCancelEventArgs e) {
// 製品の生産が中止されていない場合のみ、UnitsOnOrder の更新を許可します
int columnIndex = this.productsGridView.Columns["UnitsOnOrder"].Index;
if( e.ColumnIndex == columnIndex ) {
GridViewRow gridRow = this.productsGridView.Rows[e.RowIndex];
bool cantReorder = (bool)gridRow.Cells["Discontinued"].Value;
if( cantReorder ) {
MessageBox.Show("You cannot reorder this product ...");
e.Cancel = true;
return;
}
}
this.pnlEditing.Text = "EDIT";
}
...
}
このコードでは、"EDIT" をステータス バーに表示することによって視覚的な支援を提供し、CellBeginEdit と対になった CellEndEdit で削除されます。このイベントは、ユーザーがセルを削除するか、Esc キーを押して編集をキャンセルすることで編集を確定したときに発生します。 このイベントを使用して、ステータス バーから "EDIT" を削除しました。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.CellEndEdit +=
new System.Windows.Forms.GridViewCellEventHandler(
this.productsGridView_CellEndEdit);
...
private void suppliersGridView_CellEndEdit(
object sender,
System.Windows.Forms.GridViewCellEventArgs e) {
// ユーザーが編集を行っていないことを通知します
this.pnlEditing.Text = "";
}
...
}
また、GridView の
IsCurrentCellInEditMode プロパティをクエリして、編集モードを確認することもできます。
イベントの発生順序の編集
CellBeginEdit および CellEndEdit は、EditCellOnEnter が True に設定されている場合、以下の順序で発生します。
| シナリオ | 発生するイベント |
| あるセルから同じ行の別のセルに移動します |
- CellLeave
- CellEndEdit
- CellEnter
- CellBeginEdit
|
| あるセルから別の行の別のセルに移動します |
- CellLeave
- RowLeave
- CellEndEdit
- RowEnter
- CellEnter
- CellBeginEdit
|
EditCellOnEnter が False に設定されている場合でも、同じ順序でイベントが発生しますが、ユーザーは、RowEnter の発生後で CellBeginEdit の発生前に、F2 キーを押すか、セルをダブルクリックする必要があります。
DataError
UserDeletingRow や CellBeginEdit など、どのようなイベントにより、キャンセル操作を行うことで誤りの可能性を回避する機会が与えられるかを見てきました。 誤りの可能性を回避する別の技法としては、特に、値の型や範囲に関して、ユーザーが入力したデータを検証する技法が挙げられます。 たとえば、ユーザーが数値型 SupplierID に英数字値を設定すると、型検証に違反します。 実際、GridView により、ユーザーが有効な値を入力したり Esc キーを押さない限り、編集を確定しないようにし、選択されたセルへの入力範囲を限定することによって、この状況が発生しなくなります。このことを、図 3 に示します。
図 3. あまり明らかでない検証エラーが原因で SupplierId から移動しなくなる
今後のリリースでは、既定の動作でエラー ダイアログが表示されるようになりますが、現在の PDC "Whidbey" リリースでは、GridView により、既定のエラー "表示" メカニズムが提供されません。 ただし、どちらのリリースでも、DataError イベントを使用して GridView 例外を処理し、必要な場合はカスタム エラー メッセージを表示できます。 DataError は、以下のようなさまざまな条件下で発生する例外に一括して対応するメカニズムです。
- 編集のキャンセル中
- 編集の終了中
- 編集の確定中
- RefreshEdit の呼び出しによる編集の更新中
- セルの値が連結されたデータソースに複製されるとき
- セルの FormattedValue プロパティの設定またはセルの InitializeEditingControl メソッドの呼び出しのいずれかを行うことによる、編集するセル値の初期化中
DataError を使用して、次のように、上記の例外のうちのいずれかをキャッチできます。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.DataError +=
new System.Windows.Forms.GridViewDataErrorEventHandler(
this.productsGridView_DataError);
...
private void productsGridView_DataError(
object sender,
System.Windows.Forms.GridViewDataErrorEventArgs e) {
// エラー メッセージを解析し、ステータス バーに表示します
if( e.Exception is TargetInvocationException ) {
this.pnlMain.Text = e.Exception.InnerException.Message;
}
else this.pnlMain.Text = e.Exception.Message;
}
...
}
このコードでは、GridViewDataErrorEventArgs の
Exception プロパティで適切な例外情報が調査され、GridView が型や範囲のエラーに対してスローする TargetInvocationExceptions の Exception プロパティがさらに調査されます。 上記のコードにより、例外が抽出され、図 4 に示すように抽出された例外がステータス バーに表示されます。
図 4. DataError によってキャプチャされた検証例外の表示
実際、GridViewDataErrorEventArgs には、以下に示すようないくつかの興味深い機能が備わっています。
public class GridViewDataErrorEventArgs : System.Windows.Forms.GridViewCellCancelEventArgs {
// コンストラクタ
public GridViewDataErrorEventArgs (
System.Exception exception ,
System.Int32 columnIndex ,
System.Int32 rowIndex ,
System.Windows.Forms.GridViewDataErrorContext context )
// エラーの発生時に行われた操作の種類
public System.Windows.Forms.GridViewDataErrorContext Context { get; }
// 発生した例外
public System.Exception Exception { get; }
// 処理後に例外を再度スローします
public System.Boolean ThrowException { get; set; }
}
GridViewDataErrorEventArgs は GridViewCellCancelEventArgs から派生されるので、問題のセルの ColumnIndex と RowIndex を判断し、DataError イベントを完全にキャンセルすることもできます。 DataError イベントを完全にキャンセルすることは、さまざまなシナリオで実行する必要がある重要な操作です。 たとえば、システムで生成される検証エラーが発生すると、たとえば、別のコントロール上の GridView をクリックしたり、[閉じる] ボックスをクリックしたりすることはできません。 ただし、次のように Cancel と Context の両方のプロパティを使用することが役に立ちます。
public class Form1 : System.Windows.Forms.Form {
...
private void productsGridView_DataError(
object sender,
System.Windows.Forms.GridViewDataErrorEventArgs e) {
// エラー メッセージを解析します
string errorMessage;
if( e.Exception is TargetInvocationException ) {
errorMessage = e.Exception.InnerException.Message;
}
else {
errorMessage = e.Exception.Message;
}
// ユーザーが別のフォーム コントロールをクリックしたときにエラーが発生した場合、
// またはフォーム上の [閉じる] ボタンなどにより、警告が発生し、それに応答する場合
if( (e.Context & GridViewDataErrorContext.LeaveControl) ==
GridViewDataErrorContext.LeaveControl) {
DialogResult result = MessageBox.Show(
"Leave cell and lose changes?",
"Data Error?", MessageBoxButtons.OKCancel);
if( result == DialogResult.OK ) {
e.Cancel = false;
}
}
else {
// GridView から移動せず、エラー メッセージを表示します
this.pnlMain.Text = errorMessage;
}
}
...
}
新しいコードで最初に行われる処理は、エラーがスローされるコンテキストの確認です。この処理は特に、ユーザーが GridView コントロールから離れようとしているかどうかを確認するために行われます。 GridViewDataErrorContext により、例外がスローされたときに実行中であった処理を判断できます。 以下の表で、このような処理について説明します。
| 値 | 例外がスローされたときに試行されていた処理 |
| Formatting | セルの書式設定された値の取得 |
| Display | セルの塗りつぶし、またはセルの ToolTipText の適合 |
| PreferredSize | セルの優先サイズの計算 |
| RowDeletion | 行の削除 |
| Parsing | 編集の確定、終了、またはキャンセル |
| Commit | 編集の確定 |
| InitialValueRestoration | セル値の初期化、または編集のキャンセル |
| LeaveControl | GridView コントロールがフォーカスを失うときに行う GridView データの検証 |
| CurrentCellChange | 現在のセルが変更されるときに行うセルの内容の検証、更新、確定、または取得 |
| Scroll | 現在のセルが、スクロールの結果として変更されるときに行うセルの内容の検証、更新、確定、または取得 |
| ClipboardContent | クリップボードの内容を作成中に行うセルの書式設定された値の取得 (注 : この値は、現在の PDC "Whidbey" ビルドでは表示されません) |
ユーザーが、例外がスローされたときに GridView コントロールを離れようとしていた場合、GridView から離れることを妨害したくはありませんが、セルがエラーであったことを認識しているコントロールはユーザがそこにとどまることを確認する必要があります。 Cancel の既定値は、以前の CancelEventArgs とは異なり、True です。そのため、ユーザーはコントロールから離れることはできません。 この状況を回避するために、ユーザーがこのコントロールから離れようとしている場合は、Cancel を False に設定します。
カスタム検証
DataError は一部のエラーを捕捉しますが、DataError には完全な検証ソリューションが用意されていません。 たとえば、"入力文字列の形式が正しくありません。" など、システムによって表示されるエラー メッセージを好まない場合があります。またはユーザーが、UnitsInStock 列に負の値を入力したときにスローされた例外を回避することを希望する場合があります。この場合、データベースの CHECK 制約を指定している UnitsInStock フィールドでのデータベースの CHECK 制約の結果は、0 以上である必要があります。後者の状況の場合、検証はサーバーで行われるよりもクライアントで行われる方が常に高速になります。
セルの検証
この操作は、次のように、適切な検証コードを CellValidating イベントに配置することで実現できます。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.CellValidating +=
new System.Windows.Forms.GridViewCellFormattedValueCancelEventHandler(
this.productsGridView_CellValidating);
...
private void productsGridView_CellValidating(
object sender,
System.Windows.Forms.GridViewCellFormattedValueCancelEventArgs e) {
// 現在のセルの値を変更した場合のみ、検証を行います
if( !productsGridView.IsCurrentCellDirty ) return;
// 検証します
if( e.ColumnIndex ==
productsGridView.Columns["UnitsInStock"].Index ) {
int value = int.Parse(e.FormattedValue.ToString());
if( value < 0 ) {
pnlMain.Text = "UnitsInStock must be >= 0.";
e.Cancel = true;
}
}
}
...
}
まず、実際に編集が行われたかどうかを IsCurrentCellDirty により確認します。 編集が行われている場合、UnitsInStock 検証が失敗する場合は、Cancel を True に設定します。これにより、CellValidated と CellEndEdit が発生しなくなります。
行の検証
セルごとの検証は、セルが多数ある場合やユーザーが私のように過ちを起こしやすい場合は、ユーザーにとってわずらわしい操作になることがあります。このような状況では、行全体のデータが入力されるまで待って検証が一括して行われると、ユーザーに親切な (あまりわずらわしくない) 操作になります。 一括検証を選択する場合、GridView により RowValidating イベントと RowValidated イベントが提供されます。 お分かりのように、Products テーブルには、UnitPrice、UnitsInStock、UnitsOnOrde、および ReorderLevel のすべてが 0 以上であることを必要とする 4 つの CHECK 制約があるので、Products テーブルは行の検証モデルに適しています。 ユーザーが幸先よく開始できるように、これらのフィールドの既定値を 0 に設定することにより、型指定されたデータ セットを更新しました。その後、次のように、RowValidating を使用してこれらの各値を検証しました。
public class Form1 : System.Windows.Forms.Form {
...
this.productsGridView.RowValidating +=
new System.Windows.Forms.GridViewCellCancelEventHandler(
this.productsGridView_RowValidating);
...
private void productsGridView_RowValidating(
object sender,
System.Windows.Forms.GridViewCellCancelEventArgs e) {
// 行が変更されている場合にのみ検証します
if( !this.productsGridView.IsCurrentRowDirty ) return;
StringBuilder sb = new StringBuilder();
// 現在の行を取得します
GridViewRow row = this.productsGridView.Rows[e.RowIndex];
// UnitPrice を検証します
decimal unitPrice = decimal.Parse(row.Cells["UnitPrice"].Value.ToString());
if( unitPrice < 0 ) {
sb.AppendLine("UnitPrice must be >= 0");
}
// UnitsInStock を検証します
// UnitsOnOrder を検証します
// ReorderLevel を検証します
// メッセージを表示してキャンセルします
if( sb.ToString().Length > 0 ) {
string message = string.Format("Could not accept new values:\n\n{0}", sb.ToString());
MessageBox.Show(message, "Validation");
e.Cancel = true;
}
}
...
}
図 5 に最終結果を示します。
図 5. 実行中のカスタム検証
今回のまとめ
GridView で利用できる 130 以上のイベントの一部をかけ足で見てきたツアーをここで終了しましょう。 これらのイベントの一部は、移動の追跡や、位置による情報とコンテキストによる情報を表示するのに役立ちます。 その他のイベントは、データ入力や検証状況に役立ちます。 優れたイベントのセットがあるだけでなく、どのような場合でも、イベントには関連していて情報量が豊富なイベント引数が渡されます。このイベント引数により、作業が簡素化され、手作業で記述する必要があるコード量が減少します。 前回のコラムと同様、GridView は大規模なので、実質的には 1 つの資料で GridView のすべてのイベントを取り上げることは不可能です。また、あいにく、仮想モードのサポートなど、その他の興味深い機能のイベントについては省略しました。 実際、GridView は非常に機能的なので、現在 DataGridGirl (http://www.datagridgirl.com)が ASP.NET DataGrid コントロールで成り立っているのと同じように、GridView で成り立つ機能を容易に開発できる人もでてくるでしょう。 GridViewGuy になるでしょうか?
謝辞
Mark Rideout に感謝します。彼は専門的なアドバイスをくれ、彼自身が microsoft.beta.whidbey.windowsforms.controls ニュースグループに投稿した GridView に関する詳細情報の使用を許可してくれました。
現在、Michael Weinhardt は、Chris Sells との共著『Windows Forms Programming in C#, 2nd Edition』(Addison Wesley) など、.NET 関連のさまざまな執筆活動にフルタイムで取り組み、このコラムを執筆しています。 Michael は、総じて .NET を愛好しており、中でも特に Windows フォームを好んでいます。また、彼は可能であれば 80 年代のテレビ番組を観ています。詳細については、www.mikedub.net (英語) を参照してください。