かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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 ストアアプリにはデータストアがなかった…!!