画面を伴うプログラムを自作していて、画面から入力した値を保存しておき、次に起動したときに復元させたい時があります。
そのようなときに使える方法として、アプリケーション構成ファイル(App.config) がありますが、値を保存/復元したいコントロール1つ1つに対して、App.configへの保存/読み出しコードを記述しなければならず、それなりに面倒です。
そこで、プログラム起動時にコントロールのリストを定義しておき、一括して保存/復元できる便利クラスを作ってみましたので、紹介したいと思います。
概要
今回の自作クラス(AppSettings)は、内部で ConfigurationManager を利用しており、インスタンス生成時(new AppSettingsのとき)にApp.config から値を読み込み、コントロールに値を復元します。
また、AppSettingsの Saveメソッドを呼んだタイミングで、コントロールの値をApp.config に保存します。
ConfigurationManagerを使ったApp.configへの保存/復元方法については、こちらの記事で詳しく紹介していますので、興味のある方はご一読ください。
仕組み
インスタンス生成のタイミングで読み込んだApp.Configの値は、辞書(Dictionary)としてクラス内部に保持しています。
また、Saveメソッドにより辞書の内容をApp.config に保存しています。
引数にコントロール配列を渡した場合は、そのままクラス内部に保持すると同時に、コントロール名(XAMLに記述した x:Name の値)と辞書と突き合わせ、値を復元しています。
Saveメソッドが呼ばれると、クラス内部に保持されたコントロール配列からコントロールごとの値を取り出し、いったん辞書に反映させた後で、辞書をまるごとApp.Configに保存しています。
以上の説明から分かるように、インスタンス生成時に辞書が作成され、Saveメソッド呼び出し時にコントロールの値で辞書が上書かれるため、途中で辞書の内容を変更しても、App.Configには反映されないのでご注意ください。
保存/復元可能なコントロールの値について
コントロールが持つ全ての値を保存/復元できる訳ではありません。
現時点では、Textプロパティと、IsCheckedプロパティの値のみ保存/復元が可能です。
例えば、TextBox、TextBlock、ComboBox、CheckBox などが該当します。
これ以外のコントロール、例えばLabelやDataGrid、ListBox、DatePickerなどについても対応させたい場合は、適宜必要に応じてコードの追加をしてください。
具体的は方法は、ソースコード紹介の後で記載しています。
ユーザーコントロールの値を保存/復元するには
自作のユーザーコントロールが持つ値を保存/復元したい場合は、Textプロパティを利用する方法がおすすめです。
Textプロパティが参照された時は、ユーザーコントロール内に存在する子のコントロールの値を、カンマ又はタブコードなどで区切り、1つの文字列として返します。
Textプロパティに代入された時は、その値をSplit等で分割し、子のコントロールのプロパティに代入します。
public partial class MyUserControl : UserControl { //Textプロパティ public string Text { get {return Get(); } set { Set(value); }} public MyUserControl() { InitializeComponent(); } //引数の文字列を分解して、子のコントロールのプロパティに代入 public void Set(string text) { var values = text.Split(','); uxKey.Text = values[0]; uxShift.IsChecked = bool.Parse(values[1]); uxCtrl.IsChecked = bool.Parse(values[2]); } //子のコントロールのプロパティの値を結合して1つの文字列にする public string Get() { return String.Join(",", new string[] { uxKey.Text, uxShift.IsChecked.ToString(), uxCtrl.IsChecked.ToString(), }); } }
任意のキーによる値の保存/復元
クラス内部に保持した辞書に任意のキーで値を登録すると、それも合わせて自動で保存/復元します。
従って、コントロール以外の値(ただしテキストのみ)を管理することも可能です。
クラス内部の辞書にアクセスするには、通常の辞書と同様にインデクサを使うことが出来ます。
//インスタンスの生成 var settings = new AppSettings(); //"Posision"で文字列"999"を登録 settings["Posision"] = "999"; //"Posision"の値を取得 string str = settings["Posision"];
使い方
使い方は簡単で、値の保存/復元が必要なコントロールの配列を作成し、それを引数にしてAppSettingsのインスタンスを new で生成します。
これだけでコントロールのText又はIsCheckedプロパティの値が復元されます。
また、画面操作で変更されたコントロールの値は、保存したいタイミングでSave メソッドを呼び出すだけで完了します。
下記はそのサンプルコードです。個人的な命名規則によりコントロール名の先頭は ux で統一していますが、必ずしも ux から始める必要はありません。
var controls = new System.Windows.Controls.Control[] { uxAllScreen,uxActiveWindow,uxRectangle,uxClipboard }; //コントロール配列を引数としてインスタンスを生成 var settings = new AppSettings(controls); //コントロールの中身をapp.configに保存 settings.Save();
画面起動時に値を復元、終了時に値を保存するには
多くのケースでは、画面起動時にコントロールの値を復元し、画面終了時にコントロールの値を保存したいでしょう。
その場合、クラス変数として _settings を用意しておき、Loadedイベントハンドラ内でインスタンスを生成、_settings に格納しておきます。
そして、Closedイベントハンドラ内 にSaveメソッドを記述しておけば、画面終了時にコントロールの値が保存できます。
具体的には以下の様になります。
public partial class MainWindow : Window { //異なるハンドラ(Loaded、Closed)で共通で使うための保存用 private AppSettings _settings; //コンストラクタ public MainWindow() { InitializeComponent(); } /// <summary> /// 画面 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Window_Loaded(object sender, RoutedEventArgs e) { //値を保存/復元したいコントロール配列を作成 var controls = new System.Windows.Controls.Control[] { uxAllScreen,uxActiveWindow,uxRectangle,uxClipboard }; //インスタンスを生成 _settings = new AppSettings(controls); } /// <summary> /// 画面の終了処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Window_Closed(object sender, EventArgs e) { //コントロールの内容をapp.configに保存 _settings.Save(); } }
任意の値を保存、復元するには
任意の値をApp.configに保存したり、復元(取得)するには、クラス内部の辞書にアクセスする必要がありますが、以下の通りインデクサが利用可能です。
インスタンス変数[“任意のキー名”]
例えば、 以下の様に記述すると、”Age” というキーで “19”という文字列が登録されます。
辞書にキーがなければ新しく作られ、あれば追加されます。
また、削除するための Remove メソッドも用意しています。
var app = new AppSettings(); app["Age"] = "19"; //指定したキーで値を登録(又は変更) Console.WriteLine(app["Age"]); //指定したキーで値を取得 app.Remove("Age"); //指定したキーを削除
ConfigurationManager を使った App.config は文字列しか保存できませんので、取得する際に必要に応じて型変換を行います。
AppSettingsクラスのソースコード
以下が今回紹介したクラスのソースコードです。
Textプロパティを持つコントロールとチェックボックスに対応していますので、それで事足りる場合はそのままコピペしてお使いいただけます。
using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; using System.Windows.Input; namespace MyLib { public class AppSettings { private Configuration _configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); private Dictionary<string, string> Dic { get; set; } = new Dictionary<string, string>(); public string this[string name] { get { return Get(name); } set { Set(name, value); } } public System.Windows.Controls.Control[] Controls { get; set; } = null; public AppSettings(System.Windows.Controls.Control[] controls = null) { //コントロール配列が指定されていたら、プロパティに保存 Controls = controls; //App.configに存在するキーと値を辞書に読み込む foreach (var key in _configuration.AppSettings.Settings.AllKeys) { //キーに対応する値を取得 var value = _configuration.AppSettings.Settings[key].Value; //辞書に登録 Dic.Add(key, value); } //引数にコントロールが指定されていたら、該当するプロパティを持つコントロールに値を設定 if(Controls != null) { RestoreProperty("Text"); RestoreProperty("IsChecked"); } } /// <summary> /// コントロールに設定値を復元 /// </summary> /// <param name="propertyName"></param> private void RestoreProperty(string propertyName) { foreach (var control in Controls) { //指定したコントロール名が辞書に存在する場合 if (Dic.ContainsKey(control.Name)) { //コントロールに指定したプロパティがあるかをチェック var property = control.GetType().GetProperty(propertyName); //プロパティが見つかった場合 if (property != null) { //AppSettingsから値を取り出してプロパティに代入 if (control.GetType() == typeof(System.Windows.Controls.CheckBox)) property.SetValue(control, bool.Parse(Get(control.Name))); else property.SetValue(control, Get(control.Name)); } } } } /// <summary> /// コントロールの内容を設定値として保存 /// </summary> /// <param name="propertyName"></param> private void StoreProperty(string propertyName) { foreach (var control in Controls) { //コントロールに指定したプロパティがあるかをチェック var property = control.GetType().GetProperty(propertyName); //プロパティが見つかった場合 if (property != null) { //プロパティから値を取得してAppSettingsに保存 Set(control.Name,property.GetValue(control)?.ToString()); } } } /// <summary> /// 指定したキー名で辞書から値を参照 /// キーが存在しなければ、値が空の新しいキーを登録し、その値を返す /// </summary> /// <param name="key"></param> /// <returns></returns> public string Get(string key) { if (!Dic.ContainsKey(key)) { Set(key, ""); } return Dic[key]; } /// <summary> /// 指定したキーで辞書とApp.configに値を登録 /// キーが存在しなければ、新しいキーを登録する /// </summary> /// <param name="key"></param> /// <param name="value"></param> public void Set(string key, string value) { if (!Dic.ContainsKey(key)) { Dic.Add(key, value); _configuration.AppSettings.Settings.Add(key, value); } Dic[key] = value; _configuration.AppSettings.Settings[key].Value = value; } /// <summary> /// 指定したキーを削除する /// </summary> /// <param name="key"></param> public void Remove(string key) { Dic.Remove(key); _configuration.AppSettings.Settings.Remove(key); } /// <summary> /// App.config に保存する /// </summary> public void Save() { if (Controls != null) { StoreProperty("Text"); StoreProperty("IsChecked"); } _configuration.Save(); } } }
新しいコントロールに対応させるには
新しいコントロールに対応させるというより、新しいプロパティに対応させるといった方が適切かもしれません。
まず、コンストラクタとSaveメソッド内に、対応させたいプロパティ名を記述します。
次に、RestorePropertyメソッド内に、プロパティ毎の型変換の処理を追加します。
ここでは、コントロールがCheckBoxの場合、bool に変換していますが、これはIsCheckedのプロパティが bool 型だからです。
CheckBoxに対して複数のプロパティ(例えば IsChecked、Content)を保存したい場合、今のままでは全てbool型に変換されてしまうので、プロパティ名で分岐(例えば IsChecked なら bool 、Content なら文字列)するなどの対応が必要です。
もしRadioButton に対応させたいなら、if 文にCheckBoxとRadioButtonの2つを記述するか、あるいは コントロールを見るのではなく、プロパティ名を見て型変換を使うなどの工夫が必要です。
この辺は、ご自身の用途や好みで修正してお使いください。
まとめ
今回は、コントロールの値(=プロパティの値)をApp.configに保存/復元する際に便利な自作クラス「AppSettings」について、その仕組みと使い方の説明、ソースコード一式の紹介を行いました。
ConfigurationManager の機能を呼び出すだけの自作クラスではありますが、間に辞書を挟むことによって、コントロールの値の保存/復元がかなり便利になるかと思います。
また、任意の値についても App.config へ保存/復元が可能なので、変数の値をファイルで管理したい場合にも活用できます。
コントロールの値や変数の値をファイルに保存して、プログラム起動時に復元したい場合は、是非この記事をお役立てください。