もっと詳しく

画面を伴うプログラムを自作していて、画面から入力した値を保存しておき、次に起動したときに復元させたい時があります。

そのようなときに使える方法として、アプリケーション構成ファイル(App.config) がありますが、値を保存/復元したいコントロール1つ1つに対して、App.configへの保存/読み出しコードを記述しなければならず、それなりに面倒です。

そこで、プログラム起動時にコントロールのリストを定義しておき、一括して保存/復元できる便利クラスを作ってみましたので、紹介したいと思います。

概要

今回の自作クラス(AppSettings)は、内部で ConfigurationManager を利用しており、インスタンス生成時(new AppSettingsのとき)にApp.config から値を読み込み、コントロールに値を復元します。

また、AppSettingsの Saveメソッドを呼んだタイミングで、コントロールの値をApp.config に保存します。

AppSettingの構成図

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等で分割し、子のコントロールのプロパティに代入します。

ユーザーコントロールにおけるTextプロパティの実装イメージ図
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 メソッドを呼び出すだけで完了します。

AppSettings の利用フロー図

下記はそのサンプルコードです。個人的な命名規則によりコントロール名の先頭は 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メソッドを記述しておけば、画面終了時にコントロールの値が保存できます。

Windowにおける修正箇所のイメージ図

具体的には以下の様になります。

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 へ保存/復元が可能なので、変数の値をファイルで管理したい場合にも活用できます。

コントロールの値や変数の値をファイルに保存して、プログラム起動時に復元したい場合は、是非この記事をお役立てください。