画面を伴うプログラムを自作していて、画面から入力した値を保存しておき、次に起動したときに復元させたい時があります。
そのようなときに使える方法として、アプリケーション構成ファイル(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 へ保存/復元が可能なので、変数の値をファイルで管理したい場合にも活用できます。
コントロールの値や変数の値をファイルに保存して、プログラム起動時に復元したい場合は、是非この記事をお役立てください。