2005以降のVisual Studioを使っていれば、アプリケーションの設定の保存、復元が驚くほど簡単です。その方法は、「Visual Studioでアプリケーションの設定を保存する」で説明します。
Visual Studioを使っていなくても、.NET Framework 2.0からは、ApplicationSettingsBaseクラスを使って簡単に行うことができます。この方法は「ApplicationSettingsBaseクラスを使って設定を保存する」で説明します。
アプリケーション終了時に設定を保存しておき、次の起動時に設定を読み込むといった処理を行うためには、設定の情報をファイルに書き込むか、レジストリに書き込むかのどちらかの方法を選ぶことになるでしょう。ここではアプリケーションの設定を保存、復元するための様々な方法を考えます。
アプリケーションの設定は、それぞれの設定をバラバラの変数に入れておくのではなく、すべての設定を一つのクラスにまとめて管理しておくと便利です。
ここでは、次のような"Settings"クラスを作成し、このクラスでアプリケーションの全設定(ここでは「Text」と「Number」の2つ)を管理するものとします。
Public Class Settings Private _text As String Private _number As Integer Public Property Text() As String Get Return _text End Get Set(ByVal Value As String) _text = Value End Set End Property Public Property Number() As Integer Get Return _number End Get Set(ByVal Value As Integer) _number = Value End Set End Property Public Sub New() _text = "Text" _number = 0 End Sub End Class
public class Settings { private string _text; private int _number; public string Text { get {return _text;} set {_text = value;} } public int Number { get {return _number;} set {_number = value;} } public Settings() { _text = "Text"; _number = 0; } }
設定をファイルに保存する方法としては、WindowsではINIファイルがよく使用されていました。しかし.NET FrameworkではXMLの機能が充実していますので、XMLファイルに保存するのがよいでしょう。
補足:それでもINIファイルを使わなければならない場合は、INIファイルをテキストファイルとして扱うか、Win32 APIのGetPrivateProfileString、WritePrivateProfileString関数を使うかということになるでしょう。.NET FrameworkでINIファイルを扱うためのクラスとしては、「IniFile Class using VB.NET」、「An INI file handling class using C#」などが公開されています。
.NET FrameworkでXMLファイルを扱う方法にはいくつもありますが、「オブジェクトの内容をXMLファイルに保存、復元する」で紹介したように、設定を保存しているSettingsオブジェクトをXMLシリアル化する方法ならとても簡単です。
この方法でSettingsオブジェクト"appSettings"をXMLファイルに保存し、復元する簡単なコードを以下に示します。
Dim appSettings As New Settings '保存先のファイル名 Dim fileName As String = "C:\test\settings.config" '<XMLファイルに書き込む> 'XmlSerializerオブジェクトを作成 '書き込むオブジェクトの型を指定する Dim serializer1 As New System.Xml.Serialization.XmlSerializer(GetType(Settings)) 'ファイルを開く(UTF-8 BOM無し) Dim sw As New System.IO.StreamWriter( _ fileName, False, New System.Text.UTF8Encoding(False)) 'シリアル化し、XMLファイルに保存する serializer1.Serialize(sw, appSettings) '閉じる sw.Close() '<XMLファイルから読み込む> 'XmlSerializerオブジェクトの作成 Dim serializer2 As New System.Xml.Serialization.XmlSerializer(GetType(Settings)) 'ファイルを開く Dim sr As New System.IO.StreamReader( _ fileName, New System.Text.UTF8Encoding(False)) 'XMLファイルから読み込み、逆シリアル化する appSettings = CType(serializer2.Deserialize(sr), Settings) '閉じる sr.Close()
Settings appSettings = new Settings(); //保存先のファイル名 string fileName = @"C:\test\settings.config"; //<XMLファイルに書き込む> //XmlSerializerオブジェクトを作成 //書き込むオブジェクトの型を指定する System.Xml.Serialization.XmlSerializer serializer1 = new System.Xml.Serialization.XmlSerializer(typeof(Settings)); //ファイルを開く(UTF-8 BOM無し) System.IO.StreamWriter sw = new System.IO.StreamWriter( fileName, false, new System.Text.UTF8Encoding(false)); //シリアル化し、XMLファイルに保存する serializer1.Serialize(sw, appSettings); //閉じる sw.Close(); //<XMLファイルから読み込む> //XmlSerializerオブジェクトの作成 System.Xml.Serialization.XmlSerializer serializer2 = new System.Xml.Serialization.XmlSerializer(typeof(Settings)); //ファイルを開く System.IO.StreamReader sr = new System.IO.StreamReader( fileName, new System.Text.UTF8Encoding(false)); //XMLファイルから読み込み、逆シリアル化する appSettings = (Settings)serializer2.Deserialize(sr); //閉じる sr.Close();
設定をテキストファイルに保存する欠点として、その内容をユーザーが自由に編集できてしまうという点があります。それを防ぐためには、バイナリファイルに書き込む方法が考えられます(さらに、バイナリファイルを使ったほうが一般的に読み込み、書込みが速いです)。この方法は「オブジェクトの内容をバイナリファイルに保存、復元する」で説明しています。次はこの方法でやってみましょう。
まずは、SettingsクラスにSerializableAttributeを適用します。
<Serializable()> _ Public Class Settings '(省略) End Class
[Serializable()] public class Settings { //(省略) }
Settingsオブジェクト"appSettings"をバイナリファイルに保存し、復元するコードは、例えば次のようになります。
'Imports System.Runtime.Serialization 'Imports System.Runtime.Serialization.Formatters.Binary 'がソースファイルの一番上に書かれているものとする Dim appSettings As New Settings '保存先のファイル名 Dim fileName As String = "C:\test\settings.config" '<バイナリファイルに書き込む> 'BinaryFormatterオブジェクトを作成 Dim bf1 As New BinaryFormatter 'ファイルを開く Dim fs1 As New System.IO.FileStream(fileName, System.IO.FileMode.Create) 'シリアル化し、バイナリファイルに保存する bf1.Serialize(fs1, appSettings) '閉じる fs1.Close() '<バイナリファイルから読み込む> 'BinaryFormatterオブジェクトの作成 Dim bf2 As New BinaryFormatter 'ファイルを開く Dim fs2 As New System.IO.FileStream(fileName, System.IO.FileMode.Open) 'バイナリファイルから読み込み、逆シリアル化する appSettings = CType(bf2.Deserialize(fs2), Settings) '閉じる fs2.Close()
//using System.Runtime.Serialization; //using System.Runtime.Serialization.Formatters.Binary; //がソースファイルの一番上に書かれているものとする Settings appSettings = new Settings(); //保存先のファイル名 string fileName = @"C:\test\settings.config"; //<バイナリファイルに書き込む> //BinaryFormatterオブジェクトを作成 BinaryFormatter bf1 = new BinaryFormatter(); //ファイルを開く System.IO.FileStream fs1 = new System.IO.FileStream(fileName, System.IO.FileMode.Create); //シリアル化し、バイナリファイルに保存する bf1.Serialize(fs1, appSettings); //閉じる fs1.Close(); //<バイナリファイルから読み込む> //BinaryFormatterオブジェクトの作成 BinaryFormatter bf2 = new BinaryFormatter(); //ファイルを開く System.IO.FileStream fs2 = new System.IO.FileStream(fileName, System.IO.FileMode.Open); //バイナリファイルから読み込み、逆シリアル化する appSettings = (Settings) bf2.Deserialize(fs2); //閉じる fs2.Close();
設定を保存する場所としては、レジストリも考えられます。レジストリに書き込まれた情報はユーザーによって編集、削除される恐れが少なく、書き込みや読み込みが高速です。また、Windows 95からはINIファイルに代わる方法として推奨されていました(今でもそうかは分かりません)。ただ欠点も多いため(「Why aren't .NET "application settings" stored in the registry?」を参照)、今ではアプリケーション設定の保存場所としてはあまり使用されないようです。
レジストリにデータを書き込む方法とレジストリからデータを読み込む方法は、「レジストリの操作」で説明しています。しかし、設定を一つ一つレジストリに書き込むのは面倒です。今まで紹介してきた方法のように、設定をレジストリに保存することはできないものでしょうか?
これに対する答えが、Windows Forms FAQの「How do you persist any object in the registry」(リンク切れ)で紹介されています。ここで紹介されている方法は、BinaryFormatterによりオブジェクトをシリアル化し、得られたバイト型配列をレジストリに書き込むというものです。
この方法によりSettingsオブジェクト"appSettings"をレジストリに保存、復元するコードの例を以下に示します。
'Imports System.IO 'Imports System.Runtime.Serialization 'Imports System.Runtime.Serialization.Formatters.Binary 'がソースファイルの一番上に書かれているものとする Dim appSettings As New Settings '<レジストリに書き込む> Dim ms1 As New MemoryStream Dim bf1 As New BinaryFormatter 'シリアル化してMemoryStreamに書き込む bf1.Serialize(ms1, appSettings) 'レジストリへ保存する Dim regkey1 As Microsoft.Win32.RegistryKey = _ Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Software\test\subkey") regkey1.SetValue("Settings", ms1.ToArray()) '閉じる ms1.Close() regkey1.Close() '<レジストリから読み込む> Dim bf2 As New BinaryFormatter 'レジストリから読み込む Dim regkey2 As Microsoft.Win32.RegistryKey = _ Microsoft.Win32.Registry.CurrentUser.OpenSubKey( _ "Software\test\subkey", False) Dim bs As Byte() = CType(regkey2.GetValue("Settings"), Byte()) '逆シリアル化して復元 Dim ms2 As New MemoryStream(bs, False) appSettings = CType(bf2.Deserialize(ms2), Settings) '閉じる ms2.Close() regkey2.Close()
//using System.IO; //using System.Runtime.Serialization; //using System.Runtime.Serialization.Formatters.Binary; //がソースファイルの一番上に書かれているものとする Settings appSettings = new Settings(); //<レジストリに書き込む> MemoryStream ms1 = new MemoryStream(); BinaryFormatter bf1 = new BinaryFormatter(); //シリアル化してMemoryStreamに書き込む bf1.Serialize(ms1, appSettings); //レジストリへ保存する Microsoft.Win32.RegistryKey regkey1 = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\test\subkey"); regkey1.SetValue("Settings", ms1.ToArray()); //閉じる ms1.Close(); regkey1.Close(); //<レジストリから読み込む> BinaryFormatter bf2 = new BinaryFormatter(); //レジストリから読み込む Microsoft.Win32.RegistryKey regkey2 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey( @"Software\test\subkey", false); byte[] bs = (byte[]) regkey2.GetValue("Settings"); //逆シリアル化して復元 MemoryStream ms2 = new MemoryStream(bs, false); appSettings = (Settings) bf2.Deserialize(ms2); //閉じる ms2.Close(); regkey2.Close();
設定オブジェクトをシリアル化する方法は、とても簡単で便利です。反面、新しくプロパティを加えるとそれ以前に保存したファイルから設定を復元できなくなるかもしれないという欠点があります。つまり、バージョンアップにより新しい設定が増え、設定クラスに新しいプロパティが増えると、前のバージョンの設定が読み込めないかもしれません。
これを回避するためには、「DataContractSerializerで、上位下位互換性を保ってシリアル化、逆シリアル化できるようにする」で紹介している方法を使う、設定をHashtableのようなコレクションで管理する、DataSetとして設定を管理し、WriteXml、ReadXmlメソッドで書き込み、読み込みをする、シリアル化せずに一つ一つの設定を保存、復元するなどの方法が考えられます。
設定を保存する方法をいろいろ紹介しましたが、設定をどのフォルダ、またはレジストリキーに保存するかという問題が残っています。この問題を解決するためのプロパティがApplicationクラスにあります。
設定を保存するのに適切なフォルダのパスは、ApplicationクラスのUserAppDataPathプロパティ、LocalUserAppDataPathプロパティ、CommonAppDataPathプロパティで取得できます。
UserAppDataPathプロパティは、ユーザーのアプリケーションデータのパスを返しますので、ユーザー別に設定を保存するために使用します。具体的には、例えば、
C:\Documents and Settings\[UserName]\Application Data\[CompanyName]\[ProductName]\[ProductVersion]
のようなフォルダのパスを返します([CompanyName]、[ProductName]、[ProductVersion]はそれぞれアプリケーションのCompanyName、ProductName、ProductVersionで、[UserName]はユーザー名です)。
LocalUserAppDataPathプロパティは、非ローミングユーザー(ユーザープロファイルが格納されているシステムにログオンしたユーザー)のアプリケーションデータのパスを返します。具体的には、例えば、
C:\Documents and Settings\[UserName]\Local Settings\Application Data\[CompanyName]\[ProductName]\[ProductVersion]
のようなフォルダのパスを返します。
CommonAppDataPathプロパティは、すべてのユーザーが共有する設定を保存するために使用します。具体的には、例えば、
C:\Documents and Settings\All Users\Application Data\[CompanyName]\[ProductName]\[ProductVersion]
のようなフォルダのパスを返します。
通常設定ファイルの保存先としては、UserAppDataPathプロパティが返すパスを使えばよいでしょう。
設定を書き込むのに適切なレジストリキーは、ApplicationクラスのUserAppDataRegistryプロパティまたはCommonAppDataRegistryプロパティで取得できます。
UserAppDataRegistryプロパティは、ユーザー別に設定を保存するために使用します。具体的には、
HKEY_CURRENT_USER\Software\[CompanyName]\[ProductName]\[ProductVersion]
というキーのRegistryKeyオブジェクトを返します。
CommonAppDataRegistryプロパティは、ユーザー共通の設定を保存するために使用します。具体的には、
HKEY_LOCAL_MACHINE\Software\[CompanyName]\[ProductName]\[ProductVersion]
というキーのRegistryKeyオブジェクトを返します。
つまり、UserAppDataRegistryとCommonAppDataRegistryの違いは、ルートキーがHKEY_CURRENT_USERかHKEY_LOCAL_MACHINEかの違いです。通常は、UserAppDataRegistryに保存すればよいでしょう。
注意:Windows Vistaから導入されたUACを有効にしている場合は、CommonAppDataRegistryプロパティを呼び出すと、例外UnauthorizedAccessExceptionが発生する可能性があります。詳しくは、「Vista以降でUACが有効だとファイルの作成等に失敗する問題の対処法」をご覧ください。
ここで紹介したプロパティが返すパスは、すべて最後にアプリケーションのバージョンが付いています。よって、これらをそのまま使って設定を保存すると、バージョンごとに設定の保存先が変わるため、バージョンが変わると、前の設定を読み込めないということになります。これでは困るという時は、保存場所を自分で決めるか、バージョンが変わった時に、前のバージョンの設定を移動するようにするなどの工夫が必要になるでしょう。
補足:ここで紹介したプロパティから値を取得する時に、そのレジストリキーあるいはフォルダが存在しなかった場合、勝手に作成されます。
補足:ここで紹介したプロパティは、そのパスにアセンブリの会社名や製品名をそのまま使って生成しているため、アセンブリの会社名や製品名にファイル名として使えない文字が含まれている場合はエラーが発生しますし、"\"が使われている場合は予期せぬパスとなってしまいます。よって、アセンブリの会社名や製品名に不適切な文字が含まれていないことを確かにしてから、これらのプロパティを使用する必要があります。
以上の知識を基にして、Settingsクラスを作り直してみます。
まず、SettingsクラスのインスタンスをSettingsクラスの静的プロパティ"Instance"で取得できるようにします。
さらに、Settingsクラスの静的メソッドにより、XMLファイル、バイナリファイル、さらにレジストリへの設定の保存と復元ができるようにしています。なお保存先のパスは、バージョンに依存しない場所にしています。
Imports System Imports System.IO Imports System.Text Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters.Binary Imports System.Windows.Forms Imports Microsoft.Win32 <Serializable()> _ Public Class Settings '設定を保存するフィールド Private _text As String Private _number As Integer '設定のプロパティ Public Property Text() As String Get Return _text End Get Set(ByVal Value As String) _text = Value End Set End Property Public Property Number() As Integer Get Return _number End Get Set(ByVal Value As Integer) _number = value End Set End Property 'コンストラクタ Public Sub New() _text = "Text" _number = 0 End Sub 'Settingsクラスのただ一つのインスタンス <NonSerialized()> _ Private Shared _instance As Settings <System.Xml.Serialization.XmlIgnore()> _ Public Shared Property Instance() As Settings Get If _instance Is Nothing Then _instance = New Settings End If Return _instance End Get Set(ByVal Value As Settings) _instance = Value End Set End Property ''' <summary> ''' 設定をXMLファイルから読み込み復元する ''' </summary> Public Shared Sub LoadFromXmlFile() Dim p As String = GetSettingPath() Dim sr As New StreamReader(p, New UTF8Encoding(False)) Dim xs As New System.Xml.Serialization.XmlSerializer(GetType(Settings)) '読み込んで逆シリアル化する Dim obj As Object = xs.Deserialize(sr) sr.Close() Instance = CType(obj, Settings) End Sub ''' <summary> ''' 現在の設定をXMLファイルに保存する ''' </summary> Public Shared Sub SaveToXmlFile() Dim p As String = GetSettingPath() Dim sw As New StreamWriter(p, False, New UTF8Encoding(False)) Dim xs As New System.Xml.Serialization.XmlSerializer(GetType(Settings)) 'シリアル化して書き込む xs.Serialize(sw, Instance) sw.Close() End Sub ''' <summary> ''' 設定をバイナリファイルから読み込み復元する ''' </summary> Public Shared Sub LoadFromBinaryFile() Dim p As String = GetSettingPath() Dim fs As New FileStream(p, FileMode.Open, FileAccess.Read) Dim bf As New BinaryFormatter '読み込んで逆シリアル化する Dim obj As Object = bf.Deserialize(fs) fs.Close() Instance = CType(obj, Settings) End Sub ''' <summary> ''' 現在の設定をバイナリファイルに保存する ''' </summary> Public Shared Sub SaveToBinaryFile() Dim p As String = GetSettingPath() Dim fs As New FileStream(p, FileMode.Create, FileAccess.Write) Dim bf As New BinaryFormatter 'シリアル化して書き込む bf.Serialize(fs, Instance) fs.Close() End Sub ''' <summary> ''' 設定をレジストリから読み込み復元する ''' </summary> Public Shared Sub LoadFromRegistry() Dim bf As New BinaryFormatter 'レジストリから読み込む Dim reg As RegistryKey = GetSettingRegistry() Dim bs As Byte() = CType(reg.GetValue(""), Byte()) '逆シリアル化して復元 Dim ms As New MemoryStream(bs, False) Instance = CType(bf.Deserialize(ms), Settings) '閉じる ms.Close() reg.Close() End Sub ''' <summary> ''' 現在の設定をレジストリに保存する ''' </summary> Public Shared Sub SaveToRegistry() Dim ms As New MemoryStream Dim bf As New BinaryFormatter 'シリアル化してMemoryStreamに書き込む bf.Serialize(ms, Instance) 'レジストリへ保存する Dim reg As RegistryKey = GetSettingRegistry() reg.SetValue("", ms.ToArray()) '閉じる ms.Close() reg.Close() End Sub Private Shared Function GetSettingPath() As String Dim p As String = Path.Combine( _ Environment.GetFolderPath( _ Environment.SpecialFolder.ApplicationData), _ Application.CompanyName + "\" + Application.ProductName _ + "\" + Application.ProductName + ".config") Return p End Function Private Shared Function GetSettingRegistry() As RegistryKey Dim reg As RegistryKey = _ Registry.CurrentUser.CreateSubKey( _ ("Software\" + Application.CompanyName + _ "\" + Application.ProductName)) Return reg End Function End Class
using System; using System.IO; using System.Text; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Windows.Forms; using Microsoft.Win32; [Serializable()] public class Settings { //設定を保存するフィールド private string _text; private int _number; //設定のプロパティ public string Text { get {return _text;} set {_text = value;} } public int Number { get {return _number;} set {_number = value;} } //コンストラクタ public Settings() { _text = "Text"; _number = 0; } //Settingsクラスのただ一つのインスタンス [NonSerialized()] private static Settings _instance; [System.Xml.Serialization.XmlIgnore] public static Settings Instance { get { if (_instance == null) _instance = new Settings(); return _instance; } set {_instance = value;} } /// <summary> /// 設定をXMLファイルから読み込み復元する /// </summary> public static void LoadFromXmlFile() { string path = GetSettingPath(); StreamReader sr = new StreamReader(fileName, new UTF8Encoding(false)); System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(Settings)); //読み込んで逆シリアル化する object obj = xs.Deserialize(sr); sr.Close(); Instance = (Settings) obj; } /// <summary> /// 現在の設定をXMLファイルに保存する /// </summary> public static void SaveToXmlFile() { string path = GetSettingPath(); StreamWriter sw = new StreamWriter(fileName, new UTF8Encoding(false)); System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(Settings)); //シリアル化して書き込む xs.Serialize(sw, Instance); sw.Close(); } /// <summary> /// 設定をバイナリファイルから読み込み復元する /// </summary> public static void LoadFromBinaryFile() { string path = GetSettingPath(); FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); BinaryFormatter bf = new BinaryFormatter(); //読み込んで逆シリアル化する object obj = bf.Deserialize(fs); fs.Close(); Instance = (Settings) obj; } /// <summary> /// 現在の設定をバイナリファイルに保存する /// </summary> public static void SaveToBinaryFile() { string path = GetSettingPath(); FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write); BinaryFormatter bf = new BinaryFormatter(); //シリアル化して書き込む bf.Serialize(fs, Instance); fs.Close(); } /// <summary> /// 設定をレジストリから読み込み復元する /// </summary> public static void LoadFromRegistry() { BinaryFormatter bf = new BinaryFormatter(); //レジストリから読み込む RegistryKey reg = GetSettingRegistry(); byte[] bs = (byte[]) reg.GetValue(""); //逆シリアル化して復元 MemoryStream ms = new MemoryStream(bs, false); Instance = (Settings) bf.Deserialize(ms); //閉じる ms.Close(); reg.Close(); } /// <summary> /// 現在の設定をレジストリに保存する /// </summary> public static void SaveToRegistry() { MemoryStream ms = new MemoryStream(); BinaryFormatter bf = new BinaryFormatter(); //シリアル化してMemoryStreamに書き込む bf.Serialize(ms, Instance); //レジストリへ保存する RegistryKey reg = GetSettingRegistry(); reg.SetValue("", ms.ToArray()); //閉じる ms.Close(); reg.Close(); } private static string GetSettingPath() { string path = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData), Application.CompanyName + "\\" + Application.ProductName + "\\" + Application.ProductName + ".config"); return path; } private static RegistryKey GetSettingRegistry() { RegistryKey reg = Registry.CurrentUser.CreateSubKey( "Software\\" + Application.CompanyName + "\\" + Application.ProductName); return reg; } }
このクラスを使用して設定をXMLファイルに保存、復元するには、次のようにします。(設定を読み込む時、保存されたデータが無い場合はエラーが出ます。)
'設定を変更する Settings.Instance.Number = 123 Settings.Instance.Text = "こんにちは" '現在の設定をXMLファイルに保存する Settings.SaveToXmlFile() 'XMLファイルから設定を読み込む Settings.LoadFromXmlFile()
//設定を変更する Settings.Instance.Number = 123; Settings.Instance.Text = "こんにちは"; //現在の設定をXMLファイルに保存する Settings.SaveToXmlFile(); //XMLファイルから設定を読み込む Settings.LoadFromXmlFile();
アプリケーションの設定を保存、復元する方法には、実に様々な方法があり、ここで紹介した以外にもまだまだあるでしょう。ここで紹介した方法よりもよい方法をご存知の方は、ご連絡いただければ幸いです。
補足:設定をファイルに保存する方法として、「アプリケーション構成ファイルを使用する方法」を考えた方もいらっしゃるかもしれません。しかしアプリケーション構成ファイルは基本的に読み込み専用ですので、アプリケーションのユーザー設定の保存方法としては適切ではありません。
(この記事は「.NETプログラミング研究 第33号」で紹介したものです。)