かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Blendのデータストア

Blendにはデータのところからデータストアというものを作れます。
f:id:okazuki:20130822225859j:plain
こいつは、プロパティを定義しておいたり、XAMLからプロパティの初期値を設定できたり、Behaviorから値をセットしたりとかBindして色々やったりするのに使うと割と便利だとBlend使いの人達の間では有名?な奴です。ブラックボックスのまま使うのも気持ち悪いので、ちょっとデータストアを作ったときに生成されるコードをちょっと追いかけてみたのでメモっておきます。

データストアの定義先による違い

データストアを作成するときに、データストアの定義先を2つ選ぶことができます。

  • プロジェクト
  • このドキュメント

f:id:okazuki:20130822230400j:plain

プロジェクト

プロジェクトを選ぶと、App.xamlのResourcesにデータストアが定義されます。HogeDataStoreをプロジェクトに対して作成した後にApp.xamlを見ると以下のようになっています。

<Application
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:DataStore="clr-namespace:Expression.Blend.DataStore.HogeDataStore" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="DataStoreEdu.App"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
         
    	<DataStore:HogeDataStore x:Key="HogeDataStore" d:IsDataSource="True"/>
         
    </Application.Resources>
</Application>
このドキュメント

このドキュメントを選ぶとデータストアを作成したときに開いていたウィンドウやユーザコントロールなどのXAMLのResourcesに定義が追加されます。FugaDataSourceを、MainWindow.xamlを開いた状態で、このドキュメントに対して作成したあとにMainWindow.xamlを見ると以下のような定義が追加されています。

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DataStore="clr-namespace:Expression.Blend.DataStore.FugaDataStore" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="DataStoreEdu.MainWindow"
        Title="MainWindow" Height="350" Width="525">
	<Window.Resources>
		<DataStore:FugaDataStore x:Key="FugaDataStore" d:IsDataSource="True"/>
	</Window.Resources>
	<Grid/>
</Window>
定義先による違いのまとめ

以上のことから、定義先の違いによって、XAMLのStaticResourceを使って参照できる範囲が以下のように異なることがわかります。

  • プロジェクト
    • App.xamlに定義されているのでアプリケーション全体から参照可能
  • このドキュメント
    • WindowなどのResourcesに定義されているので、そのXAML内からしか参照できない

データストアのコード

定義先の違いによる違いはわかったので、次はデータストアのコードを眺めてみます。データストアのコード自体は、定義先が違っても変わらないので今回は最初に作成したHogeDataStoreのコードを見ていきます。

コードの場所

データストアを作成すると、プロジェクト直下にDataStoreという名前のフォルダが作成されます。そしてDataStoreの中に作成したデータストアの名前のフォルダが作られていて、その中にxamlとxaml.csとxsdという3つのファイルが作成されています。
f:id:okazuki:20130822231259j:plain
1つのデータストアを作成するとプロジェクトに以下の2つのクラスが作成されていることがソリューションエクスプローラから読み取れます。

  • HogeDataStoreGlobalStorage
  • HogeDataStore

このデータストアのクラスの名前空間は、プロジェクトとは関係なくExpression.Blend.DataStore.データストア名という命名規約で名前がついているみたいです。なんか、名前区間かぶりそうで嫌な感じです。

生成されるクラスの中身

データストアとして作成されるクラスは、2つしかありませんが、この2つのクラスが複雑に絡み合ってるのでちょっとコードを読み解くのがしんどいです。大体以下のような感じになります。

HogeDataStoreGlobalStorageクラス

Singletonというstaticなプロパティがあり、Singletonで取得したインスタンスに対して、データストアに定義したプロパティにアクセスすることができます。例えば、HogeDataStoreにProperty1というプロパティが定義されている場合は以下のようなコードでプロパティにアクセス可能です。

// 設定
HogeDataStoreGlobalStorage.Singleton.Property1 = "ほげ";
// 取得
var hoge = HogeDataStoreGlobalStorage.Singleton.Property1;

とってもグローバルで嫌な感じですので、多用は危険そうです。

HogeDataStoreクラス

HogeDataStoreGlobalStorageはシングルトンな感じなので、じゃぁHogeDataStoreなら…!と期待するところですが、このクラスは、HogeDataStoreGlobalStorageに処理を丸投げしてるだけなので、インスタンスを何個作ってもあまりいいことはありません…。例えばProperty1というプロパティのコードは以下のように丸投げコードになっています。

private string _Property1 = string.Empty;

public string Property1
{
	get
	{
		return HogeDataStoreGlobalStorage.Singleton.Property1;
	}

	set
	{
		HogeDataStoreGlobalStorage.Singleton.Property1 = value;
	}
}

しかも、こいつのコンストラクタではHogeDataStore.xamlから値を読み込んでデータの初期化とかをやってるので、newするだけでHogeDataStoreGlobalStoregaの中身がリセットされてしまいます。超危険です。コンストラクタのコードを以下に示します。

public HogeDataStore()
{
	try
	{
		System.Uri resourceUri = new System.Uri("/DataStoreEdu;component/DataStore/HogeDataStore/HogeDataStore.xaml", System.UriKind.Relative);
		if (System.Windows.Application.GetResourceStream(resourceUri) != null)
		{
			HogeDataStoreGlobalStorage.Singleton.Loading = true;
			System.Windows.Application.LoadComponent(this, resourceUri);
			HogeDataStoreGlobalStorage.Singleton.Loading = false;
			HogeDataStoreGlobalStorage.Singleton.Register(this);
		}
	}
	catch (System.Exception)
	{
	}
}

因みにXAMLはデフォルトではこんな感じになってます。値を設定してるだけですね。

<DataStore:HogeDataStore xmlns:DataStore="clr-namespace:Expression.Blend.DataStore.HogeDataStore" Property1="値"/>

実際にリセットされるのか試してみました。

// 値を設定しても
HogeDataStoreGlobalStorage.Singleton.Property1 = "ほげ";
// HogeDataStoreのインスタンスを作成すると…
var store = new HogeDataStore();
// リセットされちゃう
MessageBox.Show(HogeDataStoreGlobalStorage.Singleton.Property1);

このメソッドを実行すると、以下のように"値"と表示されました。
f:id:okazuki:20130822233301j:plain

この挙動は、おそらくWindowのインスタンスが生成されたタイミングで、Windowに定義されてるデータストアの値をリセットするためのものだと思われます。そのため、同じWindowを複数表示するとデータストアのデータが意図せぬタイミングでリセットされます。同じWindowを複数表示するようなアプリケーションでは利用を控えたほうが幸せそうです。

まとめ

ということで、データストアを使うときは以下のように使うといいでしょう。

  • "このドキュメント"を選択する場合はそのWindowが同時に複数表示されないようにすること
  • コードからDataStoreをnewしないこと
  • コードからDataStoreのデータにアクセスするときはHogeDataStoreGlobalStorage.Singletonを通じてアクセスすること

ということになります。そもそも単一ページしか表示されないようなWindows PhoneアプリやWindowsストアアプリでは、同時に同じページが表示されることなんてないのでnewさえしなければ問題ないと思います。だが、Windows ストアアプリにはデータストアがなかった…!!