Formの位置を記録する(アプリケーション設定 その1)

Formの位置をユーザーごとに記録しておいて、次回起動時に同じ位置に表示する、みたいなことが.NET2.0から簡単にできるようになった。位置の記録と読み出し程度なら、ちょちょっとマウス操作して数行のお決まりのコードを貼り付けるだけでできてしまう。VisualStudioってすんごいねぇ。
利用する機能は、アプリケーション設定の中の「ユーザー設定」というもの。名前通り、ユーザーごとに設定を保存してくれる。アプリケーション設定はアプリごとに一つあるけど、ユーザー設定はユーザーごとに別のファイルになるのでウィンドウ位置やオプション設定なんかのユーザーお好みの設定を記録しておくのに使える。記録するにはSave()、旧バージョンからの設定引継ぎはUpgrade()なんて書くだけ。楽だ。
以下、Visual C# 2005 Expressで実際にWindow位置を記録してみる手順。かかる時間は3〜5分程度(!?)なので、ユーザー設定って何?って人は実際に試してみてほしい。


1.「ファイル→新しいプロジェクト」で新規Windowsアプリケーションを作成。
2.Form1.csのデザイン画面で、Formのプロパティの「(ApplicationSettings)」を見つける。カテゴリ順ならデータの下にある。こいつが今回いじってみるアプリケーション設定君。この下にいろいろあるので、左の「+」をクリックして展開する。
3.すぐ下に出てきた「(PropertyBinding)」を選んで「...」をクリック。すると、「Form1のアプリケーション設定」というポップアップ画面が出てきて、簡単記録できる項目がずら〜っと並んでいる。
4.今回は位置の記録を試してみるので、「Location」を選び、右のコンボボックスを開き、下に出てきた「(新規...)」をクリック。
5.すると「新しいアプリケーション設定」画面が出てくる。Nameだけ空欄なので、適当に「FormLocation」とでもつける。DefaultValueはすでに(0,0)になってて、ScopeはUserになっている。このScopeがUserかApplicationかってところが、ユーザー設定かアプリケーション設定かっていう違い。
6.「OK」をクリックすると、先ほどの「Form1のアプリケーション設定」画面に「FormLocation」の名前が反映されるので、こちらも「OK」をクリック。
7.あとはFormを閉じたときにセーブする機能をつけるだけ。なぜかセーブだけ自動でやってくれない(VBはセーブも自動らしい)。ソリューションエクスプローラの参照設定に「System.Configuration」を追加。同じくソリューションエクスプローラからForm1.csのコードを開いて、先頭に「using System.Configuration;」を追加する。
8.最後にForm1クラスに以下のコードを追加する。

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

9.これで完成!コンパイル&実行すると左上(0,0)座標にFormが表示される。右下にでも移動してFormを閉じ、もう一回起動してみると、今度は右下に出てくる!


なんとも便利な機能だ。詳しい機能の解説はWindows フォームのアプリケーション設定あたり。VBでの解説も参考になる。この解説に「何らか理由で、Size プロパティはバインド可能なプロパティとして表示されません。 これはこのバージョンでの設計上の判断ですが、最終版ではすべてのプロパティがバインドできるようになります。」ってあるけど、結局Sizeは表示されていない。ClientSizeがあるけど...。
当然デザイナ画面だけからではなく、コードで書くこともできる。が、デザイナと手書きを混ぜるとものすごく使いづらい機能だったりもする。このへんはまた後ほど。
(追記)コンパイル通らなかったのを修正しました。すみません。

ユーザー設定カラクリ編とヒント(アプリケーション設定 その2)

あんまり深くないカラクリ解説編。
まずはFormから値がどこにどう設定されているのかってあたり。Form1.Designer.csを開いてみてみると、Properties.Settings.Defaultにデータがバインドされてるのが分かる。
データバインディングでLocationが変わるたびに何かコードがごそごそ動くのは無駄すぎて嫌って場合は、デザイナ画面の(PropertyBinding)での設定を行わず、プロジェクトのプロパティの設定画面でデータの定義だけを行い、データの設定と読み取りだけをコードで実現することもできる。FormのOnLoadで

try
{
	this.Location = Properties.Settings.Default.FormLocation;
}
catch ( ConfigurationErrorsException )
{
}

として読み取り、OnClosingで

Properties.Settings settings = Properties.Settings.Default;
settings.FormLocation = this.Location;
try
{
	settings.Save();
}
catch ( ConfigurationErrorsException ce )
{
	MessageBox.Show( ce.BareMessage, "Error!" );
}
finally
{
	base.OnClosing( e );
}

として書き込めばOK。
デザイナでの設定は楽だけど使いづらい面もあって、ちょっとしたアプリ程度なら使えるが、こった事をしたい場合は使えないと思ったほうがよい。例えば、永続化する設定値に、アプリケーション設定の属性を独自に定義することができないようだ。こういうときはデザイナ画面での設定をあきらめ、手でコードを書くしかないようだ(それでもSave()なんて書けるのでユーザー設定を使うメリットは大きい)。

次にユーザー設定ファイルの場所。ClickOnceかWinFormかどうか、ローミングプロファイルを使ってるかどうか、デバッグ時はvshost有効かどうか、などによって変わってくる。AssemblyInfo.csのCompany名、バージョン設定もファイル位置にかかわる。詳しくはこのへん。WinFormで特に設定もしないと「Documents and Settings\username\Local Settings\Application Data」の下にできた(ローミングなし)。ファイル名はuser.config。
user.configファイルはバージョンごとにフォルダが作られ、ファイルが変わる。AssemblyInfo.csでバージョンが時間で変わるようにしていると(最後の桁が*とか)、開発時にはなんともうっとうしい。
プロジェクトのプロパティを開いて「設定」を見るとアプリケーション設定が見れる。その1の例ならFormLocationだけが表示される。この画面の左上にある「同期」を押すと、古い設定ファイルをいっぺんに削除できる。でも、AssemblyInfo.csでCompanyが入っていないと見つけれないっぽい。
さらに、同期の右にある「コードの表示」を押すと、Settings.csというファイルが生成される!Settings.csは「設定の検証」にある、設定値の検証用コードの雛形になっている。設定値の検証を行うことができるタイミングは4つ。ロード時、セーブ時、値変更前、値変更後。最低でもロード時は必ず検証が必要だ。user.configはXMLなので、エディタなどで簡単に値を変更できる。範囲外の値に書き換えられてたら、デフォルト値に変更するなどのコードがどうしても必要になる。SettingChangingで検証するなら、すべての変更時に呼ばれるようなので、その他のタイミングでの検証は不要になる。
Settings.csはSettings.Designer.csのpartialになっている。Settings.Designer.csは「アプリケーション設定のアーキテクチャ」にある、設定の定義相当。プロジェクトのプロパティの設定画面で値を追加すると、このファイルとapp.configファイルに追加分が書き込まれる。
逆にapp.configに設定を手書きで追加すると、プロジェクトのプロパティの設定とSettings.Designer.csに自動的に取り込まれる!なので、デザイナでユーザー設定を追加した場合、手書きで値を追加するのがものすごく困難になる。というか、どうやったら両方で共存できるのか分からない...。一つでも手書きによる追加が必要になったら、すべて手書きするしかないのかも...。(こっちにやり方を書いた)

手書きするにも、デザイナで生成されたコードが参考になるので、一度デザイナに生成させたコードを見てみると早い。MSDN「方法 : アプリケーション設定を作成する」は、突然SettingsやappSettingsなんてのが出てきて混乱する。単純ミスだろうけど。このドキュメントの前にデザイナのコードを読んだほうがいい。

ユーザー設定を記録するようにすると、バージョンが変わった際の設定値の取り扱いが問題になってくる。旧バージョンの設定はばっさり切り捨てるってのもありだけど(^^;、できるものだけでも引き継ぎたいところ。Upgrade()として古い設定を読み取る仕組みは用意されている。Upgrade()に関するヒントは「LocalFileSettingsProvider.Upgrade」や「ApplicationSettingsBase.Upgrade」あたりが参考になる。

とりあえずこのあたりまで調べたので記録。
(追記)ちょっとはしょってたところを追加。
(さらに追記)OnClosingのコード修正。前のでも問題ない。
(さらにさらに追記)例外捕捉処理を追加。