あらかじめ日記

雑記とかブログで使えるスクリプトとかテクニックとか。その他、音楽やアニメ、漫画の話題とかも。

【C#】WinFormでDataBindingsを実装してみる(そしてRadioButtonが曲者)


最近WPFで、MVVMパターンをかじっているのですが、そこでポイントとなるのがバインドと言う機能。
簡単に言うと、コントロールのプロパティとそれを保持するデータのプロパティを関係付け(バインドし)、UIのクラスとロジックのクラスの分離と連係を行う機能ですが、説明は他の詳しいサイト様にお任せするとして、この機能はWinFormでも使えます。

 双方のプロパティが更新される度に自動反映されるので、この機能を使えばこんな処理とももうおさらば。。

Text  = textBox1.Text;
Check = checkBox1.Checked;
 ・
 ・
 ・


プログラムではこのように書きます。

    public bool Check { get; set; }

    public Form1 ()
    {
        InitializeComponent ();
    
        checkBox1.DataBindings.Add("Checked", this, "Check");
    }
   ※きちんと設計するならUIとプロパティのクラスは分けるべきですが、ここでは省略します

Visual Studioのデザイナ上でもウィザードに従って設定可能です。クラス名 - プロパティ名と書いても設定できました。

ただし、データのプロパティの更新に対するコントロール側の反映はINotifyPropertyChangedを継承し、プロパティのset時にINotifyPropertyChangedが持つPropertyChangedイベントハンドラを呼び出して設定したことを通知する必要があります。

プロパティ毎に処理を入れるのは少々面倒ではあるのですが、フォームロード前にプロパティに設定している値はコントール側に反映されるようなので、オプション画面のような、単に入力値を取得するだけでプロパティへの設定は初期値だけのような場合は、バインドしておくだけで十分そうです。

ちなみに、コントロールからプロパティに反映するタイミングは、DataSourceUpdateModeNever(反映しない)、OnPropertyChangedプロパティ変更時)、OnValidation値の検証後(フォーカスアウトのタイミング等))があります。
プロパティからの反映もControlUpdateModeで設定できます。(これにはOnValidationはありません)


さて、ここで困ったのがRadioButton

RadioButtonはグループ内のRadioButton同士で、チェックを入れたら他方のチェックが外れる、と言う標準的な動作を勝手に行ってくれますが、これがどうもDataBindingsとの相性が悪い。

RadioButtonにチェックを入れた直後は良いのですが、期待としてはそのままコントロールのチェック状態に従ってプロパティに反映してほしいものの、どうもコントロール同士の連携を行う処理の中でバインドされたプロパティを改めて反映し直しているのか、プロパティから反映されたことでまたグループ内の別のRadioButtonのチェックが変わってしまい・・・と、コントロール間のデータ反映が上手く動作してくれず期待通りの結果になりません。

DataSourceUpdateModeOnValidationの場合は、チェック入れた直後は、見た目上チェックが入っていますが、値の検証後プロパティへの反映が行われると、チェックが戻ってしまいます。

方や、OnPropertyChangedだとチェックと同時にプロパティに反映されるのですが、今度は全てチェックが外れた状態になってしまいます。

どうもグループ内でtrueのものが同時に存在してしまうことが都合が悪そうで、タイミングがOnValidationであればプロパティの値が反映される前にCheckedChangedイベントなどで他のRadioButtonとバインドされたプロパティをfalseにしてしまえば回避できそうです。

OnPropertyChangedだと、即時更新されるためにCheckedChangedイベントでは遅くこの方法では上手く行きません。

これを回避させるために色々と試してみたところ、プロパティの値に引きずられている(っぽい)ので、プロパティの更新タイミングであるControlUpdateModeを「Never」(反映しない)にしておけば予想通り、チェックを入れる場合は変な動作は起きずに期待通りの動きになりました。

ただし、Neverなのでプロパティからの反映は行わないことが前提です。ただNeverに設定するタイミングをFormLoad時にしておけば、初期値の反映は行なえました。

プロパティからの反映も行いたい場合は、多少強引な気もしますがCheckedChangedのタイミングでControlUpdateModeNeverにし、プロパティのsetterのタイミングで(正確にはプロパティを持つクラスのPropertyChangedイベントハンドラFormで受けて)ControlUpdateModeを戻す、と言うことをすれば一応動作しそうです。

もし、この辺り上手い方法はあるのでしょうか。正確な方法ありましたらぜひご教示ください。。