DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

設定プロバイダを自作して、アプリケーション設定がレジストリに保存されるようにする

注意:ここで紹介している方法は、.NET Framework 2.0以降でのみ有効です。

ここでは、SettingsProviderクラスから派生した設定プロバイダを自作する方法を紹介します。設定プロバイダを自作することにより、ApplicationSettingsBaseクラスを使用した設定の管理をどのようにするかを決めることができます。例えば、.NET Frameworkで用意されている設定プロバイダLocalFileSettingsProviderでは設定をファイルに保存しますが、設定プロバイダを自作することにより、レジストリに保存することもできるようになります。ここでも具体的に、アプリケーション設定をレジストリに保存する設定プロバイダを作成する方法を紹介します。

ヘルプでは、「アプリケーション設定アーキテクチャ」や「SettingsProvider クラス」でカスタム設定プロバイダに関する説明があります。さらに「RegistrySettingsProvider のサンプル」では、まさにレジストリに設定を保存する設定プロバイダのサンプルがあります。これらも参考にしてください。

SettingsProvider派生クラスの作成

設定プロバイダは、SettingsProviderクラスを継承して作成します。SettingsProviderクラスは抽象クラスで、ApplicationNameプロパティと、GetPropertyValues、SetPropertyValuesメソッドをオーバーライドする必要があります。

ApplicationNameプロパティでは、現在実行中のアプリケーションの名前を返すようにします。ヘルプでは、Getで「System.Reflection.Assembly.GetExecutingAssembly().GetName().Name」を返す例が書かれています(Setは空です)。また、「RegistrySettingsProvider のサンプル」では、Getで「Application.ProductName」を返しています。しかし、次のようにすることにより、アプリケーションの名前がApplicationNameに入るようです。

VB.NET
コードを隠すコードを選択
Private _applicationName As String = String.Empty
Public Overrides Property ApplicationName() As String
    Get
        Return Me._applicationName
    End Get
    Set(ByVal value As String)
        Me._applicationName = value
    End Set
End Property
C#
コードを隠すコードを選択
private string _applicationName = string.Empty;
public override string ApplicationName
{
    get
    {
        return this._applicationName;
    }
    set
    {
        this._applicationName = value;
    }
}

GetPropertyValuesメソッドは、データの保存先から設定を取得する必要があるときに呼び出されます。つまり、このメソッドで保存先のレジストリから設定を読み込みます。

SetPropertyValuesメソッドは、ApplicationSettingsBase.Saveメソッドにより設定が保存されるときに呼び出されます。つまり、このメソッドで設定をレジストリに保存します。

必ずオーバーライドしなければならない抽象メンバは以上です。しかし、Initializeメソッドもオーバーライドする必要があります。Initializeメソッドでは、基本クラスのInitializeメソッドを呼び出すだけでよいでしょう。このとき、設定プロバイダの名前を第一パラメータに指定します。

設定プロバイダのの具体例

簡単な説明でしたが、具体的なコードを見ていただいたほうが早いでしょう。以下にレジストリに設定を保存する設定プロバイダの例を示します。参照にSystem.Configuration.dllを追加する必要があります。

VB.NET
コードを隠すコードを選択
Imports System.Windows.Forms
Imports System.Configuration
Imports Microsoft.Win32

Namespace Dobon.Sample.Configuration
    Public Class RegistrySettingsProvider
        Inherits SettingsProvider

        Private _applicationName As String
        ''' <summary>
        ''' 現在実行中のアプリケーションの名前
        ''' </summary>
        Public Overrides Property ApplicationName() As String
            Get
                Return Me._applicationName
            End Get
            Set(ByVal value As String)
                Me._applicationName = value
            End Set
        End Property

        ''' <summary>
        ''' 設定プロバイダ名
        ''' </summary>
        Private _name As String

        ''' <summary>
        ''' コンストラクタ
        ''' </summary>
        Public Sub New()
            Me._applicationName = String.Empty
            Me._name = "RegistrySettingsProvider"
        End Sub

        Public Overrides Function GetPropertyValues( _
                ByVal context As SettingsContext, _
                ByVal collection As SettingsPropertyCollection) _
                As SettingsPropertyValueCollection
            Dim col As New SettingsPropertyValueCollection()

            Dim prop As SettingsProperty
            For Each prop In collection
                'SettingsPropertyに基づいて、SettingsPropertyValueを作成
                Dim val As New SettingsPropertyValue(prop)
                'レジストリに保存されている設定の値を取得
                'SerializedValueにシリアル化された値を格納
                val.SerializedValue = Me.ReadSettingFromRegistry(context, prop)
                val.IsDirty = False
                'コレクションに追加
                col.Add(val)
            Next prop

            Return col
        End Function

        Public Overrides Sub SetPropertyValues( _
                ByVal context As SettingsContext, _
                ByVal collection As SettingsPropertyValueCollection)
            Dim spv As SettingsPropertyValue
            For Each spv In collection
                '値が変更されているか調べる
                If spv.IsDirty Then
                    'レジストリに書き込む
                    Me.WriteSettingToRegistry(context, spv)
                    spv.IsDirty = False
                End If
            Next spv
        End Sub

        Public Overrides Sub Initialize(ByVal pname As String, _
            ByVal config As System.Collections.Specialized.NameValueCollection)
            '設定プロバイダ名を指定する
            MyBase.Initialize(Me._name, config)
        End Sub

        ''' <summary>
        ''' 指定された設定がユーザースコープか調べる
        ''' </summary>
        ''' <param name="setting">調べる設定プロパティ</param>
        ''' <returns>ユーザースコープならばTrue</returns>
        Private Function IsUserSetting(ByVal setting As SettingsProperty) _
                As Boolean
            Dim isUser As Boolean = setting.Attributes.Contains( _
                GetType(UserScopedSettingAttribute))
            Dim isApp As Boolean = setting.Attributes.Contains( _
                GetType(ApplicationScopedSettingAttribute))
            If isUser And isApp Then
                Throw New ConfigurationErrorsException( _
                    "設定""" + setting.Name + _
                    """にApplicationScopedSettingAttributeと" + _
                    "UserScopedSettingAttributeの両方が指定されています。")
            End If
            If Not isUser And Not isApp Then
                Throw New ConfigurationErrorsException( _
                    "設定""" + setting.Name + _
                    """にApplicationScopedSettingAttributeまたは" + _
                    "UserScopedSettingAttributeを指定する必要があります。")
            End If
            Return isUser
        End Function

        ''' <summary>
        ''' レジストリから指定された設定を読み込む
        ''' </summary>
        ''' <param name="context">SettingsContext</param>
        ''' <param name="prop">読み込む設定プロパティ</param>
        ''' <returns>取得された値</returns>
        Private Function ReadSettingFromRegistry( _
                ByVal context As SettingsContext, _
                ByVal prop As SettingsProperty) As Object
            Dim regKey As RegistryKey = Me.GetSettingRegistryKey(context, prop)
            Try
                If regKey Is Nothing Then
                    Return Nothing
                End If 'レジストリから値を読み込む
                '指定されたキーが無ければ、デフォルト値を返す
                Return regKey.GetValue(prop.Name, prop.DefaultValue)
            Finally
                regKey.Close()
            End Try
        End Function

        ''' <summary>
        ''' レジストリに設定の値をを保存する
        ''' </summary>
        ''' <param name="context">SettingsContext</param>
        ''' <param name="spv">書き込む設定プロパティ</param>
        Private Sub WriteSettingToRegistry( _
                ByVal context As SettingsContext, _
                ByVal spv As SettingsPropertyValue)
            Dim regKey As RegistryKey = _
                Me.GetSettingRegistryKey(context, spv.Property)
            Try
                If regKey Is Nothing Then
                    Return
                End If
                'SerializedValueの値を書き込む
                regKey.SetValue(spv.Name, spv.SerializedValue)
            Finally
                regKey.Close()
            End Try
        End Sub

        ''' <summary>
        ''' 指定された設定プロパティに対応したレジストリキーを取得する
        ''' </summary>
        ''' <param name="context">SettingsContext</param>
        ''' <param name="prop">設定プロパティ</param>
        ''' <returns>レジストリキー</returns>
        Private Function GetSettingRegistryKey( _
                ByVal context As SettingsContext, _
                ByVal prop As SettingsProperty) As RegistryKey
            Dim pRegKey As RegistryKey
            If Me.IsUserSetting(prop) Then
                pRegKey = Application.UserAppDataRegistry
            Else
                pRegKey = Application.CommonAppDataRegistry
            End If
            'セクション名のサブキーを作成
            Dim secName As String = Me.GetSectionName(context)
            Dim regKey As RegistryKey = pRegKey.CreateSubKey(secName)
            pRegKey.Close()

            Return regKey
        End Function

        ''' <summary>
        ''' セッション名を取得する
        ''' </summary>
        ''' <param name="context">SettingsContext</param>
        ''' <returns>セッション名。"GroupName.SettingsKey"となる。</returns>
        Private Function GetSectionName( _
                ByVal context As SettingsContext) As String
            Dim ret As String = CStr(context("GroupName"))
            Dim setKey As String = CStr(context("SettingsKey"))
            If Not String.IsNullOrEmpty(setKey) Then
                ret += "." + setKey
            End If
            Console.WriteLine(ret)
            Return ret
        End Function
    End Class
End Namespace
C#
コードを隠すコードを選択
using System.Windows.Forms;
using System.Configuration;
using Microsoft.Win32;

namespace Dobon.Sample.Configuration
{
    public class RegistrySettingsProvider : SettingsProvider
    {
        private string _applicationName;
        /// <summary>
        /// 現在実行中のアプリケーションの名前
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return this._applicationName;
            }
            set
            {
                this._applicationName = value;
            }
        }

        /// <summary>
        /// 設定プロバイダ名
        /// </summary>
        private string _name;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public RegistrySettingsProvider()
        {
            this._applicationName = string.Empty;
            this._name = "RegistrySettingsProvider";
        }

        public override SettingsPropertyValueCollection GetPropertyValues(
            SettingsContext context, SettingsPropertyCollection collection)
        {
            SettingsPropertyValueCollection col =
                new SettingsPropertyValueCollection();

            foreach (SettingsProperty prop in collection)
            {
                //SettingsPropertyに基づいて、SettingsPropertyValueを作成
                SettingsPropertyValue val = new SettingsPropertyValue(prop);
                //レジストリに保存されている設定の値を取得
                //SerializedValueにシリアル化された値を格納
                val.SerializedValue =
                    this.ReadSettingFromRegistry(context, prop);
                val.IsDirty = false;
                //コレクションに追加
                col.Add(val);
            }

            return col;
        }

        public override void SetPropertyValues(SettingsContext context,
            SettingsPropertyValueCollection collection)
        {
            foreach (SettingsPropertyValue spv in collection)
            {
                //値が変更されているか調べる
                if (spv.IsDirty)
                {
                    //レジストリに書き込む
                    this.WriteSettingToRegistry(context, spv);
                    spv.IsDirty = false;
                }
            }
        }

        public override void Initialize(string pname,
            System.Collections.Specialized.NameValueCollection config)
        {
            //設定プロバイダ名を指定する
            base.Initialize(this._name, config);
        }

        /// <summary>
        /// 指定された設定がユーザースコープか調べる
        /// </summary>
        /// <param name="setting">調べる設定プロパティ</param>
        /// <returns>ユーザースコープならばTrue</returns>
        private bool IsUserSetting(SettingsProperty setting)
        {
            bool isUser = setting.Attributes.Contains(
                typeof(UserScopedSettingAttribute));
            bool isApp = setting.Attributes.Contains(
                typeof(ApplicationScopedSettingAttribute));
            if (isUser && isApp)
            {
                throw new ConfigurationErrorsException("設定\"" + setting.Name +
                    "\"にApplicationScopedSettingAttributeと" +
                    "UserScopedSettingAttributeの両方が指定されています。");
            }
            if (!isUser && !isApp)
            {
                throw new ConfigurationErrorsException("設定\"" + setting.Name +
                    "\"にApplicationScopedSettingAttributeまたは" +
                    "UserScopedSettingAttributeを指定する必要があります。");
            }
            return isUser;
        }

        /// <summary>
        /// レジストリから指定された設定を読み込む
        /// </summary>
        /// <param name="context">SettingsContext</param>
        /// <param name="prop">読み込む設定プロパティ</param>
        /// <returns>取得された値</returns>
        private object ReadSettingFromRegistry(SettingsContext context,
            SettingsProperty prop)
        {
            using (RegistryKey regKey =
                this.GetSettingRegistryKey(context, prop))
            {
                if (regKey == null)
                    return null;
                //レジストリから値を読み込む
                //指定されたキーが無ければ、デフォルト値を返す
                return regKey.GetValue(prop.Name, prop.DefaultValue);
            }
        }

        /// <summary>
        /// レジストリに設定の値をを保存する
        /// </summary>
        /// <param name="context">SettingsContext</param>
        /// <param name="spv">書き込む設定プロパティ</param>
        private void WriteSettingToRegistry(SettingsContext context,
            SettingsPropertyValue spv)
        {
            using (RegistryKey regKey =
                this.GetSettingRegistryKey(context, spv.Property))
            {
                if (regKey == null)
                    return;
                //SerializedValueの値を書き込む
                regKey.SetValue(spv.Name, spv.SerializedValue);
            }
        }

        /// <summary>
        /// 指定された設定プロパティに対応したレジストリキーを取得する
        /// </summary>
        /// <param name="context">SettingsContext</param>
        /// <param name="prop">設定プロパティ</param>
        /// <returns>レジストリキー</returns>
        private RegistryKey GetSettingRegistryKey(SettingsContext context,
            SettingsProperty prop)
        {
            RegistryKey regKey;
            if (this.IsUserSetting(prop))
                regKey = Application.UserAppDataRegistry;
            else
                regKey = Application.CommonAppDataRegistry;

            //セクション名のサブキーを作成
            string secName = this.GetSectionName(context);
            if (!string.IsNullOrEmpty(secName))
                regKey = regKey.CreateSubKey(secName);

            return regKey;
        }

        /// <summary>
        /// セッション名を取得する
        /// </summary>
        /// <param name="context">SettingsContext</param>
        /// <returns>セッション名。"GroupName.SettingsKey"となる。</returns>
        private string GetSectionName(SettingsContext context)
        {
            string ret = (string)context["GroupName"];
            string setKey = (string)context["SettingsKey"];
            if (!string.IsNullOrEmpty(setKey))
                ret += "." + setKey;

            return ret;
        }
    }
}

この設定プロバイダでは、ユーザースコープの設定をApplication.UserAppDataRegistryの返すレジストリキー(HKEY_CURRENT_USER\Software\CompanyName\ProductName\ProductVersion)以下に保存します。アプリケーションスコープの設定も、値の変更が可能で、Application.CommonAppDataRegistryの返すレジストリキー(HKEY_LOCAL_MACHINE\Software\CompanyName\ProductName\ProductVersion)以下に保存します。

RegistrySettingsProvider のサンプル」との違いは、設定プロパティにApplicationScopedSettingAttributeとUserScopedSettingAttributeの両方が指定されているか、どちらも指定されていないときに例外をスローする点や、保存場所をセッション名で分けている点などです。

設定プロバイダの使用法

自作した設定プロバイダを設定クラスで使用するには、設定クラスにSettingsProviderAttribute属性を適用します。つまり、次の例のようにします。

VB.NET
コードを隠すコードを選択
<SettingsProvider(GetType(Dobon.Sample.Configuration.RegistrySettingsProvider))> _
Public Class TestSettings
    Inherits ApplicationSettingsBase
End Class
C#
コードを隠すコードを選択
using System.Configuration;

[SettingsProvider(typeof(Dobon.Sample.Configuration.RegistrySettingsProvider))]
public class TestSettings : ApplicationSettingsBase
{
}

IApplicationSettingsProviderの実装

さらにIApplicationSettingsProviderを実装することにより、ApplicationSettingsBaseのUpgrade、GetPreviousVersion、Resetメソッドによる、設定のアップグレードなどの機能が有効になります。詳しい方法は、後日紹介する予定です。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、レジストリへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。