Blendにはデータのところからデータストアというものを作れます。
こいつは、プロパティを定義しておいたり、XAMLからプロパティの初期値を設定できたり、Behaviorから値をセットしたりとかBindして色々やったりするのに使うと割と便利だとBlend使いの人達の間では有名?な奴です。ブラックボックスのまま使うのも気持ち悪いので、ちょっとデータストアを作ったときに生成されるコードをちょっと追いかけてみたのでメモっておきます。
データストアの定義先による違い
データストアを作成するときに、データストアの定義先を2つ選ぶことができます。
- プロジェクト
- このドキュメント
プロジェクト
プロジェクトを選ぶと、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つのファイルが作成されています。
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);
このメソッドを実行すると、以下のように"値"と表示されました。
この挙動は、おそらくWindowのインスタンスが生成されたタイミングで、Windowに定義されてるデータストアの値をリセットするためのものだと思われます。そのため、同じWindowを複数表示するとデータストアのデータが意図せぬタイミングでリセットされます。同じWindowを複数表示するようなアプリケーションでは利用を控えたほうが幸せそうです。
まとめ
ということで、データストアを使うときは以下のように使うといいでしょう。
- "このドキュメント"を選択する場合はそのWindowが同時に複数表示されないようにすること
- コードからDataStoreをnewしないこと
- コードからDataStoreのデータにアクセスするときはHogeDataStoreGlobalStorage.Singletonを通じてアクセスすること
ということになります。そもそも単一ページしか表示されないようなWindows PhoneアプリやWindowsストアアプリでは、同時に同じページが表示されることなんてないのでnewさえしなければ問題ないと思います。だが、Windows ストアアプリにはデータストアがなかった…!!