Entry

Visual StudioのApplicationSettingsを使ってFormコントロールの値の保存・読込みを行う

.NETのWindows Formアプリケーションで、コントロールの設定を保存する方法を色々調べてみたので、その内容を簡単に書く。何か思ったより面倒臭かった、というか色々間違えたせいで無駄に苦労した。多分、知ってる人にとっては大した内容ではないと思う。
対象言語はC#。なお、ここでいう設定の保存とは、コントロールの値(テキストボックスの入力値など)を標準で作成されるSettingsオブジェクト(Properties.Settings.Defaultで呼び出せるやつ)に格納する事を指す。

今回やりたかったのは、アプリケーションの設定変更画面を作って、「OK」ボタンを押した場合はコントロールの内容を設定に保存、「キャンセル」を押した場合は保存しない、というよくある挙動を作る事。元々別の方法で作ってたけど、何となくMicrosoftの流儀に従って作ってみることにした。ここでは、各コントロールには対応する設定がバインドされている事が前提。例えば、次の図のようにコントロールのApplicationSettingsに設定(ここでは、SenderUserのこと)がバインドされている状態となっていること。(ちなみに、この実態はSenderUserがバインドされているだけなので、DataBindingにも自動的に反映されている)

コントロールのプロパティ

この状態では、バインドしたデータの値が自動的にコントロールに反映されるようになる。ただし、この状態でアプリケーションからコントロールの値を変更すると、変更結果が即時設定に保存されることになる。「OK」「キャンセル」ボタンで値を保存する/しないを制御したいので、これでは具合がよろしくない。
この場合、図のようにバインドのデータソース更新モード(DataSourceUpdateModeプロパティ)を「Never」に変更することで、自動的に反映されなくなる。

DataBindingの詳細設定

ただし、各コントロールに一つ一つ設定していくと大変だし、変更漏れやバグの温床なので、以下のような処理で一括設定するのがいいかも。コントロールオブジェクトが読み込まれた後となる、FormのLoadイベント内で1度実行するのが無難と思われる。ちなみに、ソース内の「BindedControl」はForm内のコントロールからBindingsを持ってるコントロールを再帰的に別途取得したリスト。

            // コントロールの変更をデータソースに反映しない
            foreach (Control c in BindedControls)
            {
                foreach (Binding bind in c.DataBindings)
                {
                    bind.DataSourceUpdateMode = DataSourceUpdateMode.Never;
                }
            }

なお、データソース更新モードを画面から設定した場合は、最初に設定を読みに行かなくなるため、コントロールの値を設定から手動で読み込むために、以下のような処理が必要になる。

            // データソースの値をコントロールに設定
            foreach (Control c in BindedControls)
            {
                foreach (Binding bind in c.DataBindings)
                {
                    bind.ReadValue();
                }
            }

これで、設定画面を開いた時に各コントロールにバインドされた設定値がコントロールに設定される。
ちなみに、他の方法として『「キャンセル」の場合は設定を書き戻す』ような挙動も考えられるが、操作中の設定はそのまま実際の設定として反映されてしまい、バックグラウンドでアプリケーションが動いていた場合は操作途中の設定がユーザーの意図しない動作を引き起こしてしまうため、望ましくない。

次は、設定画面でコントロールを操作(チェックボックスのON/OFF切り替え、テキストボックスの文字入力等)して値を変更した後、その値を設定に書き戻す方法について。これは、設定画面で「OK」ボタンを押した時のイベント等に記述することになる。
設定を書き込むためにはBindingオブジェクトにWriteValueというメソッドがある。これを、単純に連続して実行すると最初に実行されるWriteValueメソッドだけが有効になり、以降の設定値は反映されない事象が発生する。これは(多分)、最初の1件の値が設定に書き込まれた時点で、新しい設定値で全てのコントロールの値を更新するという処理が走るため。その結果、2件目以降のコントロールの値は元に戻されてしまい、最初の1件しか設定値に反映されないという事象が起こる。
これを防ぐためには、設定が変更されてもコントロールに反映しないよう、BindingオブジェクトのControlUpdateModeプロパティを「Never」に設定する。一括で設定するためには、以下のような処理となる。ちなみに、これをFormLoadイベント内で実行するとコントロールに値が設定されなくなるので、設定を書き込む直前辺りに実行すると良い(多分)。

            // データソースの変更をコントロールに反映しない
            foreach (Control c in BindedControls)
            {
                foreach (Binding bind in c.DataBindings)
                {
                    bind.ControlUpdateMode = ControlUpdateMode.Never;
                }
            }

で、次にコントロールの値を設定に保存する処理。

            // コントロールの値をデータソースに設定
            foreach (Control c in BindedControls)
            {
                foreach (Binding bind in c.DataBindings)
                {
                    bind.WriteValue();
                }
            }

なお、この2つの処理をくっつけて以下のように書くと、やはり最初の1件しか設定値に反映されなくなって失敗する。ControlUpdateModeプロパティの値に関係なく、WriteValueメソッドが実行された時点でコントロールが更新されるようだ(自分はここで勘違いしててハマった)。面倒でも別々に記述して、値を保存するコントロール全部のControlUpdateModeプロパティを一旦Neverに設定してから、WriteValueメソッドを実行する。

            // コントロールの値をデータソースに設定
            foreach (Control c in BindedControls)
            {
                foreach (Binding bind in c.DataBindings)
                {
                    bind.ControlUpdateMode = ControlUpdateMode.Never;
                    bind.WriteValue();
                }
            }

以上が、Formコントロール値の保存・読込み処理について今回学んだ事。

ちなみに、設定をファイルに保存する場合は通常、Properties.Settings.DefaultのSave()メソッドを使うけれど、自分は個人的にあまり好きではないので、SQLiteで保存するようにしている。まぁ、それはまた別の話。

おまけ

BindedControlsを取得するメソッドは以下のような感じ。Formオブジェクトを引数に取れば、Form内の全てのデータがバインドされたコントロールを取得する。複数のデータソースがバインドされてる場合はもう少し考えるけど、今回は単一データソースなので気にしない。


        private Control[] GetBindedControls(Control baseControl)
        {
            List<Control> controlList = new List<Control>();
            foreach (Control c in baseControl.Controls)
            {
                // データがバインドされたコントロールのみ取得
                if (c.DataBindings != null && c.DataBindings.Count > 0) controlList.Add(c);
                controlList.AddRange(GetBindedControls(c));
            }
            return controlList.ToArray();
        }

Comments (0 件)

コメントを残す

メールアドレスが公開されることはありません。