アプリケーション設定 その3

id:siokoshou:20060122 でアプリケーション設定を VisualStudio と手書きで混在させる方法が分からないって書いてた件、解決できたのでメモしときます。以下は前回に引き続きユーザー設定の話題を扱ってます。

プロジェクトのプロパティを開くと出てくる画面(プロジェクトデザイナっていうみたい)の「設定」ページ(以下、設定ページ)で、アプリケーション設定とユーザー設定(各ユーザーごとの設定)が簡単に設定できるってとこは前回書いたとおり。設定ページで追加した設定は app.config に反映されて、コンパイルすると最終的には「ほげほげ.exe.config」になります。
設定ページは楽なんだけどできることがかなり限定されてて、アプリケーション設定で用意されてる機能のごく一部しか使えません。かといってせっかく用意されてるツールを捨てて、app.config と ApplicationSettingsBase を継承したクラスを全部手書きするのはもったいない。そこで設定ページでできることは設定ページを使いつつ、できない部分だけ手書きするという共存の道を探ってみました(どこかに情報が載ってそうだけどまだ見つけてない…)。

どうやら app.config に VisualStudio が書き込んだのとは別の section を用意してやれば、共存可能なようです。そうするとユーザー設定セクションが複数あることになりますが、別セクションには何を書いても VisualStudio は干渉してきません。設定値を読み書きする処理は Settings.cs に追加でOKでした。
app.config の VisualStudio が書き込んだ section に手書きで追加してしまうと、VisualStudio が設定ページに自動的に取り込み、さらに Settings.Designer.cs に読み書きするコードも自動で生成してしまいます。こうなると設定ページで app.config が壊れているって警告がでたり、Settings.cs に手書きしたコードと同じプロパティを読み書きするコードが Settings.Designer.cs に重複してできてしまいコンパイルが通らなくなります。

ちょっと整理。

ファイル 書き換えOK?
app.config 書き換えOKだけど、VisualStudioも読み書きするので、書き換えてもよいところとマズイところがある。
Settings.cs 設定ページの「コードの表示ボタン」を押すと生成される。書き換えOK。
Settings.Designer.cs VisualStudioが自動生成する。書き換えちゃダメ!


例がないとややっこしくて何言ってんだかわかんねー、ってわけで以下サンプルで順を追って説明します。


id:siokoshou:20060122#p1 に、Form の Size を手書きで追加します。このくらいのことは設定ページでもできるけど、サンプルってことで。

前回までの時点の app.config は以下のとおり。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="userSettings" (中略) >
      <section name="WindowsApplication1.Properties.Settings" (中略) />
    </sectionGroup>
  </configSections>
  <userSettings>
    <WindowsApplication1.Properties.Settings>
      <setting name="FormLocation" serializeAs="String">
        <value>0, 0</value>
      </setting>
    </WindowsApplication1.Properties.Settings>
  </userSettings>
</configuration>

sectionGroup として userSettings が定義されていて、今ここに一つだけある section の WindowsApplication1.Properties.Settings が VisualStudio が自動生成したセクションです。これとは別のセクションを追加すると、追加した section には VisualStudio が干渉してきません。そこに Size を追加してやります。

先に進む前に、問題点が何のことだか分からないって方は「」の後ろに「」なんて追加して設定ページを開くと、自動的に取り込むところを見れます。Settings.Designer.cs を開いてみるとコードまで追加されてます。設定ページ→コードの一方向の生成ツールではなく、両者で同期を取る賢いツールだってのが分かります。こいつはどうやら別セクションは無視してくれるようだよってのが今回の話題。

で、話を戻して、まずは sectionGroup 内に section の定義を追加します。WindowsApplication1.Properties.Settings の定義をコピペして、ManualSettings とでも名付けてしまえばOK。name 以外の type やら何やらはそのまま。
次に後ろのほうの userSettings にこのセクションを追加し、FormSize の名前(何でもいいけど)で Size の定義を追加します。
追加後の app.config はこうなります。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="userSettings" (中略) >
      <section name="WindowsApplication1.Properties.Settings" (中略) />
      <section name="ManualSettings" (中略) />
    </sectionGroup>
  </configSections>
  <userSettings>
    <WindowsApplication1.Properties.Settings>
      <setting name="FormLocation" serializeAs="String">
        <value>0, 0</value>
      </setting>
    </WindowsApplication1.Properties.Settings>
    <ManualSettings>
      <setting name="FormSize" serializeAs="String">
        <value>300, 300</value>
      </setting>
    </ManualSettings>
  </userSettings>
</configuration>

app.config の詳しいことは「.NET Framework の構成ファイル スキーマ」のあたり。

Settings.cs に読み書きする処理を追加します。この例は設定ページでも設定できる程度のことなので Settings.Designer.cs からコピペしてちょっと直しただけ。検証コードはサンプルなので省略。

[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute( "300, 300" )]
public global::System.Drawing.Size FormSize
{
	get
	{
		return ( ( global::System.Drawing.Size ) ( this[ "FormSize" ] ) );
	}
	set
	{
		this[ "FormSize" ] = value;
	}
}

Form1.cs に読み書き処理を追加。

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Configuration;

namespace WindowsApplication1
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();

			this.Size = Properties.Settings.Default.FormSize;
		}

		protected override void OnClosing( CancelEventArgs e )
		{
			try
			{
				Properties.Settings.Default.FormSize = this.Size;
				Properties.Settings.Default.Save();
			}
			catch ( ConfigurationErrorsException ce )
			{
				MessageBox.Show( ce.BareMessage, "Error!" );
			}
			finally
			{
				base.OnClosing( e );
			}
		}
	}
}

ついでに AssemblyInfo.cs の Company に test とでも書いて、保存された user.config を見てみてネ。ちょっと驚きます。

コンパイル実行すると、Form の Size を保存/読み取りするようになります。サイズを変更して終了、もう一度実行するとサイズが終了前と同じになってるハズ。

このように VisualStudio が作ったセクションとは別のセクションであれば設定ページは無視してくれるようです。これで設定ページを使いつつも、serializeAs="Binary" なんてのは手書きするってことができます。
正しい方法なのかどうかよく分かってないけど、まぁ多分OK。違うこうやるんだって情報や、ここ見ると書いてあるヨって情報があったら教えてくださいませ。