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を持ってるコントロールを再帰的に別途取得したリスト。

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

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

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

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

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

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

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

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

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

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

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

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

おまけ

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

[csharp]

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();
}

[/csharp]


Posted by:

Posted At:

Modified At:

Category:

コメント

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください