Windows Formの検証機能

Windows Formの検証機能は2.0で大幅に強化されそれなりに実用的になっている。
しかし、案外癖があって???状態になることも多い。
以前部分的に書いたことがあったのだが、利用上のポイントを簡単にまとめてみたい。

Validatingイベント

最初にWindows Formで検証を行うために用意された最も基本的な仕組みは各コントロールValidatingイベントである。
Validatingイベントの処理では入力された値のチェックを行い、もしエラーがあればエラーメッセージを表示したりする処理を記述する。CancelEventArgsのCancelプロパティをtrueにするとそのコントロールにフォーカスを残すことができる。

private void textBox1_Validating(object sender, CancelEventArgs e)
{
    int work;
    if (!int.TryParse(textBox1.Text, out work)) e.Cancel = true;
}
CausesValidationプロパティ

次に、重要なポイントが各コントロールCausesValidationプロパティである。分かってしまえばたいしたことは無いのだが、どの様にフォームの検証メカニズムに影響を与えるか知っておく必要がある。

パターン1 CausesValidationがtrueからtrueへのコントロールの移動
この場合、移動元のコントロールのValidatingイベントが発生する
パターン2 CausesValidationがtrueからfalseへのコントロールの移動
この場合には移動先が検証メカニズムに参加していないため、移動元のコントロールのValidatingイベントは発生しない。
パターン3 CausesValidationがfalseからfalseへのコントロールの移動
このパターンはパターン2と同じになる。
パターン4 CausesValidationがfalseからtrueへのコントロールの移動
このパターンは少し複雑で、移動元のコントロールは検証メカニズムに参加していないためValidatingイベントは発生していないのだが、それよりも前の移動元でCausesValidationがtrueのコントロールがあるとそのコントロールのValidatingイベントが発生するという動きをする。

これらから、CausesValidationを単に移動元のコントロールのValidatingイベント発生を制御するフラグというよりも、フォームの検証メカニズムに参加するかどうかを表しているということになる。
あと、PanelなどのコンテナのCausesValidationも影響するので注意が必要。要はPanelのCausesValidationがtrueになっているとその中のButtonのCausesValidationがfalseでもValidatingイベントが発生するということ。

Validate・ValidateChildrenメソッド

ここからが2.0の新機能であるが、
まずは、FormのValidateメソッドValidateChildrenメソッドである。
これらはいずれも、プログラムから明示的にValidatingイベントを発生させる仕組みである。ValidateChildrenを利用するとまとめてValidatingイベントを発生させることもできようになっている。

良く出てくるケースとして、BindingNavigationの保存ボタンを押してもValidatingイベントが発生しないのだが、これはToolStripButtonのCausesValidationがfalseになっているためで、Validateメソッドで明示的に移動元のValidatingイベントを発生させることで対処することができる

AutoValidateメソッド

次にValidatingの動作自体を制御するためのFormのAutoValidateプロパティであるが、これを利用するとValidatingイベントの発生の有無を制御することができる。指定可能なオプションは、Disable・EnableAllowFocusChange・EnablePreventFocusChange・Inheritの4つである。Diableを指定するとフォームが発生させる暗黙的なValidatingを抑制することができる。たとえば、フォームのClose処理を実行すると暗黙的なValidatingが発生してしまい困ることがあるが、以下のように記述することで回避することができる。

//button1.CausesValidation = falseに設定されている必要がある
private void button1_Click(object sender, EventArgs e)
{
    this.AutoValidate = AutoValidate.Disable;
    this.Close();
}

関連して、コントロールボタンのxを選択した場合にValidatingイベントが発生して画面がクローズされない問題についてはFormClosingをハンドリングして対処する。

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    e.Cancel = false;
}

ただ、この場合コントロールのValidatingイベント自体は発生してしまうのでこれを対処する場合、WndProcをオーバーライドするようなことで対処が必要になってくる。

//たぶん動作すると思うけど未確認コード
private const int WM_CLOSE = 0x10;
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_CLOSE)
    {
        AutoValidate = AutoValidate.Disable;
    }
    base.WndProc(ref m);
}
Binding.DataSourceUpdateModeプロパティ

最後にデータバインドとのからみでBinding.DataSourceUpdateMode について、DataSourceUpdateModeはOnValidation・OnPropertyChanged・Neverの3つのモードがあるが、ここではOnValidationの場合の動作、Validatingイベントでプロパティが正常に検証されるとデータソースが更新される点だけ取り合えず記憶しておく。逆に言うとValidatingイベントが発生しないとデータソースに入力値が反映されない点に注意しておけばよいということになる。
ちなみに、ComboBoxなどで選択した値によって他の入力コントロールの状態を変えたいような場合、ComboBoxのSelectedItemをOnPropertyChangedにして即座に反映させる方法もあるが、SelectedIndexChangedでWriteValueを実行する方法もある。

追加メモ

AutoValidate.Disableでフォーカス移動させると一時的に移動先のコントロールが検証メカニズムから外れるためかわからないが、移動先のコントロールから次のコントロールに移動した場合に1つ前のコントロールのValidatingが行われる