Control#SelectNextControlメソッドとデータバインド

WindowsFormsで双方向のデータバインドを行っている場合、バインドしたコントロールのプロパティ値と対応するデータソースオブジェクト(DTO)のプロパティは互いを更新しあうようになる。
例えばテキストボックスのTextプロパティと、PersonDTOという単純なクラスのNameプロパティを双方向データバインドした場合

1. コントロールのTextプロパティ値変更 -> PersonDTOのNameプロパティ変更
2. PersonDTOのNameプロパティ変更 -> コントロールのTextプロパティ値変更

これらが自動化されるわけだが、前者の方向のバインド処理が実施されるタイミングつまりコントロールのプロパティ値がデータソース側に反映されるタイミングは、Bindingクラスのインスタンスを生成した際のパラメタであるDataSourceUpdateMode列挙体の値に依存する。

DataSourceUpdateMode列挙体のとりうる値は

Never
OnPropertyChanged
OnValidation

この3つだが、通常使うのは一番下のOnValidation(検証時)だろう。

OnValidationを使った場合、値の検証はコントロールのフォーカス消失後に実施されて、その後INotifyPropertyChangedインタフェースを実装しているはずのPersonDTOのPropertyChangedイベントが発火するはずである。

標題のメソッドControl#SelectNextControlは次にフォーカスを当てることのできるコントロールを探索して(必要であれば親コントロールもパース対象にして)、フォーカスを移す処理を行う。

ここからが本題だが、このメソッドを使用すると前述したコントロールからPersonDTOへのデータバインドが発生しないことがある。(PropertyChangedイベントが発火しない)
現在解っているケースだが、TextBoxコントロールのTextChangedイベントの処理中に同メソッドを使って、他のコントロールにフォーカスを移す場合だ。(私の環境だけの問題かもしれないことをお断りしておく)

protected virtual void ExTextChangeHandler(object sender, EventArgs e)
{
    TextBoxBase tb = sender as TextBoxBase;
    Form parent = tb.FindForm();
    if (parent != null )
    {
        if ( tb.Text.Length >= tb.MaxLength )
        {
            parent.SelectNextControl(tb, true, true, true, true); //何故かデータバインドが実行されない
        }
    }
}

実はこの問題はBindingオブジェクトを生成する際のDataSourceUpdateModeパラメタをDataSourceUpdateMode.OnPropertyChangedにすることで避けることはできる。がそうするとテキストボックスで文字を一つ入力する度にデータバインドが発生することになり、パフォーマンスの点で不利になってしまうことがあるだろう。

あくまで、DataSourceUpdateMode.OnValidationを使ったままでこの問題を回避する方法だが、以下のようにBindingオブジェクトを取得して、WeiteValueメソッドで強制的にコントロールからデータソースに値を書き込むことで可能だ。

protected virtual void ExTextChangeHandler(object sender, EventArgs e)
{
    TextBoxBase tb = sender as TextBoxBase;
    Form parent = tb.FindForm();
    if (parent != null )
    {
        if ( tb.Text.Length >= tb.MaxLength )
        {
            foreach (Binding binding in tb.DataBindings)
            {
                binding.WriteValue(); //フォーカスを移す前に強制書き換え
            }
            parent.SelectNextControl(tb, true, true, true, true);
        }
    }
}

いつものことだが、これがベストの方法かどうかは現状では解らないので悪しからず。