XAMLコードだけでデータ・ソースのインスタンスを生成し、それをデータ・コンテキストに設定してVisual Studioのデザイン画面に表示する方法を説明する。
powered by Insider.NET
前回までで紹介したデータ・バインドの方法では、実際にどのような表示になるかは実行してみないと分からない。実行する前にVisual Studioのデザイン画面で表示を確かめることはできないだろうか?
そこで本稿では、XAMLコードだけでデータ・ソースのインスタンスを生成し、それをデータ・コンテキストに設定してデザイン画面に表示する方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #34(Windows 8版)」と「Windows Store app samples:MetroTips #34(WP 8版)」からダウンロードできる。
なお、掲載しているコードは特記なき場合はWindowsストア・アプリとWindows Phone 8アプリで共通である。
●事前準備
Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。
Windows Phone 8(以降、WP 8)向けのアプリを開発するには、SLAT対応CPUを搭載したPC上の64bit版Win 8 Pro以上とWindows Phone SDK 8.0(無償)が必要となる。
●XAMLコードでデータ・コンテキストを設定するには?
以前の記事(「文字列をコントロールにバインドするには?[Win 8/WP 8]」と「文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」)では、コードビハインドのコードでデータ・ソースをデータ・コンテキストに設定していた。一方、VS 2012のデザイン画面ではコードビハインドのコードは実行されない。そのため、デザイン画面の表示には反映されず、実際にはどのような表示になるか分からなかったのである。
ならば、XAMLのコードだけでデータ・ソースのインスタンスを生成してデータ・コンテキストに設定できれば、デザイン画面の表示にも反映されるだろう。それは、以下のようにすれば実現できる。
まず、XAMLのコードだけでデータ・ソースのインスタンスを生成しなければならない。そのようなインスタンスを生成する方法は、実はすでに「文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」で紹介している。そのときは、バリュー・コンバータのインスタンスを定義するためにページの<Resources>要素内に記述した。同様にしてデータ・ソースのインスタンスも定義できるのだ。
例えば前回でリファクタリングしたClockクラスのインスタンスを定義するには、ページの<Resources>要素内に次のように記述する。まず、Win 8用のコードを示す。次のコードの太字の部分がClockクラスのインスタンスの定義だ。
<Page.Resources>
…… 省略 ……
<local:Clock x:Key="ClockInstance" />
<local:DateTimeToHhMmSsConverter x:Key="DateTimeValueConverter" />
<common:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Page.Resources>
一方、WP 8用のXAMLコードは次のようになる。
<phone:PhoneApplicationPage
…… 省略 ……
xmlns:local="clr-namespace:MetroTips034"
xmlns:common="clr-namespace:MetroTips034.Common"
>
<phone:PhoneApplicationPage.Resources>
…… 省略 ……
<local:Clock x:Key="ClockInstance" />
<local:DateTimeToHhMmSsConverter x:Key="DateTimeValueConverter"/>
<common:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</phone:PhoneApplicationPage.Resources>
続いて、上で定義したインスタンスをコントロールのデータ・コンテキストに設定する。これには、コントロールのDataContextプロパティを使う。例えば、TextBlockコントロールでは次のようにXAMLコードを記述する。
<TextBlock x:Name="textClock1" …… 省略 ……
DataContext="{StaticResource ClockInstance}"
Text="{Binding NowTime}" />
これで、ページの<Resources>要素内で定義したClockクラスのインスタンスがTextBlockコントロールのデータ・コンテキストに設定され、TextBlockコントロールのTextプロパティはそのインスタンスのNowTimeプロパティとバインドされる。以上はXAMLコードだけで記述されているので、次の画像のようにデザイン画面でも表示される。これで実際の画面表示がどうなるかをデザイン時に確認できるようになる。
●データ・コンテキストをまとめて設定するには?
以上で実際の表示がどうなるかをデザイン時に確認できるようになったが、コントロールごとにデータ・コンテキストを記述するのは面倒だ。実は、データ・コンテキストは親のコントロールのものを引き継ぐという特徴がある*1ので、各コントロールの親コントロールにデータ・コンテキストの設定を1回記述するだけで、全ての子コントロールでこの設定が使われるようになるのだ。例えば次のようにTextBlockコントロールの親に当たるStackPanelコントロールのデータ・コンテキストを設定すると、子に当たるTextBlockコントロールとEllipseコントロールにも同じデータ・コンテキストが引き継がれる。
*1 その特徴があるから、「Data Context(=データの文脈)」と呼ばれるのであろう。
<StackPanel Grid.Row="1" Margin="120,40,40,0"
DataContext="{StaticResource ClockInstance}" >
<StackPanel Margin="0,0,0,0">
<TextBlock Text="DateTimeOffset をそのままバインド" FontSize="21" />
<TextBlock x:Name="textClock1" FontSize="120" Foreground="DarkGoldenrod"
Text="{Binding NowTime}" />
</StackPanel>
<StackPanel Margin="0,20,0,0">
<TextBlock Text="DateTimeOffset をバリュー・コンバータを介してバインド"
FontSize="21" />
<TextBlock x:Name="textClock2" FontSize="120" Foreground="DarkCyan"
Text="{Binding NowTime, Converter={StaticResource DateTimeValueConverter}}" />
</StackPanel>
<StackPanel Margin="0,20,0,0">
<TextBlock Text="bool をバリュー・コンバータを介して Visibility プロパティにバインド"
FontSize="21" />
<Ellipse x:Name="RedCircle" Width="90" Height="90" Fill="DarkRed"
HorizontalAlignment="Left"
Visibility="{Binding IsEven, Converter={StaticResource BoolToVisibilityConverter}}" />
<Ellipse x:Name="GreenCircle" Width="90" Height="90" Fill="Green"
HorizontalAlignment="Left"
Visibility="{Binding IsOdd, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</StackPanel>
●データ・コンテキストをページに設定するには?
上の例ではStackPanelコントロールにデータ・コンテキストを設定したが、それならば、配置された全てのコントロールの親である、 ページそのものにデータ・コンテキストを設定してもよいだろう。
ただし、ページの開始タグ内に記述してもうまくいかない。次のようにして、ページの<Resources>要素の直後に*2ページの<DataContext>要素を追加する。
…… 省略 ……
</Page.Resources>
<Page.DataContext>
<Binding Source="{StaticResource ClockInstance}"/>
</Page.DataContext>
…… 省略 ……
…… 省略 ……
</phone:PhoneApplicationPage.Resources>
<phone:PhoneApplicationPage.DataContext>
<Binding Source="{StaticResource ClockInstance}"/>
</phone:PhoneApplicationPage.DataContext>
…… 省略 ……
*2 実際には、ページの<Resources>要素の直後でなくても、ページの最初の子要素のレベル(=ページの<Resources>要素と同レベル)であれば、どこに記述してもよい。
以上の設定を行えば、StackPanelコントロールに設定した「DataContext="{StaticResource ClockInstance}"」を削除しても、時間が表示されるはずだ。時間が表示されないときには、デザイン画面を一度閉じてから、再度オープンしてみよう。
●デザイン時と実行時でデータ・コンテキストを使い分けるには?
デザイン画面でデータをバインドする基本は以上であるが、3点ほど補足しておく。1点目は「デザイン時属性」(または「設計時属性」)だ。
デザイン時と実行時で、データ・コンテキストに設定するインスタンスを使い分けたい場合がある。Clockクラスのようなシンプルなものでは必要ないだろうが、例えばデータベースのサービスからデータを取得するようなデータ・ソースの場合は、デザイン時にはダミーのデータを表示させ、実行時には取得してきたデータを表示させるようにしたいだろう。
このようなときに使うのがデザイン時属性だ。デザイン時属性は「d:」プレフィックスを付けて表される。また、デザイン時属性は実行時には無視される。デザイン時属性がないときには、「d:」プレフィックスを持たない同名の属性がデザイン時/実行時の両者で使われる。
そこで、例えば先ほどのページのデータ・コンテキストを、次のようにデザイン時属性に置き換えると、実行時には表示されなくなる。
<d:Page.DataContext>
<Binding Source="{StaticResource ClockInstance}"/>
</d:Page.DataContext>
そして、改めてコードビハインドのコードで、実行時に表示させるデータをページのデータ・コンテキストに設定する。例えば、通常の時刻ではなく世界標準時を表すGmtClockクラスが用意してあるとすると、ページのLoadStateメソッドに次のように記述する([新しいアプリケーション(XAML)]プロジェクト・テンプレートからプロジェクトを作成した場合とWP 8の場合は、Windows.Ui.Xaml.Controls.PageクラスあるいはMicrosoft.Phone.Controls.PhoneApplicationPageクラスにLoadStateメソッドがなくこれをオーバーライドできないので、コンストラクタに以下のコードを記述する)。
protected override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
this.DataContext = new GmtClock();
}
Protected Overrides Sub LoadState(navigationParameter As Object, _
pageState As Dictionary(Of String, Object))
Me.DataContext = New GmtClock()
End Sub
これで、デザイン時にはClockクラスのプロパティが、実行時にはGmtClockクラスのプロパティが表示される。
実際の開発では上記のように、デザイン時のデータ・コンテキストはXAMLコードで、実行時のそれはコードビハインドで設定することが多い。しかし次に示すように、XAMLコードで両方とも設定可能だ。この場合、デザイン時には通常のDataContext属性をデザイン時属性で上書きすることになる。
<Page.Resources>
……省略……
<local:GmtClock x:Key="GmtClockInstance" />
……省略……
</Page.Resources>
<Page.DataContext>
<Binding Source="{StaticResource GmtClockInstance}"/>
</Page.DataContext>
<d:Page.DataContext>
<Binding Source="{StaticResource ClockInstance}"/>
</d:Page.DataContext>
●部分的に異なるデータ・コンテキストを設定するには?
2点目は、部分的に異なるデータ・コンテキストを設定する方法だ。
ページのデータ・コンテキストはページ内の全てのコントロールに適用されるのだが、一部のコントロールに別のデータ・コンテキストを設定することもできる。その場合は、その別に設定をしたコントロールの子に当たるコントロールにも、そのデータ・コンテキストが適用される。
例えば次のXAMLコードは「文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」で紹介した画面を改修したものだ。GmtClockクラスのインスタンスをデータ・コンテキストに設定したStackPanelコントロールとその子に当たるコントロールはGmtClockクラスを使い、それ以外のコントロールではページのデータ・コンテキストに設定されたClockクラスを使うようになっている。
<Page.Resources>
…… 省略 ……
<local:Clock x:Key="ClockInstance" />
<local:GmtClock x:Key="GmtClockInstance" />
</Page.Resources>
<Page.DataContext>
<Binding Source="{StaticResource ClockInstance}"/>
</Page.DataContext>
…… 省略 ……
<StackPanel Grid.Row="1" Margin="120,40,40,0">
<StackPanel Margin="0,0,0,0">
<TextBlock Text="DateTimeOffset をそのままバインド" FontSize="21" />
<TextBlock x:Name="textClock1" FontSize="120" Foreground="DarkGoldenrod"
Text="{Binding NowTime}" />
</StackPanel>
<StackPanel x:Name="StackPanelTime2" Margin="0,20,0,0"
DataContext="{StaticResource GmtClockInstance}">
<TextBlock Text="DateTimeOffset をバリュー・コンバータを介してバインド"
FontSize="21" />
<TextBlock x:Name="textClock2" FontSize="120" Foreground="DarkCyan"
Text="{Binding NowTime, Converter={StaticResource DateTimeValueConverter}}" />
</StackPanel>
…… 省略 ……
</StackPanel>
このときのデザイン画面は次の画像のようになる。
また、個別に設定を行う際にも実行時のデータ・コンテキストとデザイン時のデータ・コンテキストを別個に設定が可能だ。これについては公開するサンプルのMainPage.xamlファイルを参照してほしい。
●デザイン時か実行時かを判別するには?
最後に、データ・ソースのコードなどで、デザイン時と実行時でロジックを切り替えたいときがある。そのようなときに、どちらのモードで動作しているかを判別するためのプロパティが用意されている。
●まとめ
ページの<Resources>要素にデータ・ソースのインスタンスを定義し、ページの<DataContext>要素にそのインスタンスをバインドすれば、ページ内の全てのコントロールのデータ・コンテキストにそれが設定される。必要ならば個別にデータ・コンテキストを設定することもできる。
また、デザイン時と実行時でデータ・コンテキストを使い分けられるし、コード内でデザイン時と実行時の判別も可能だ。
デザイン時のデータ・バインドの詳細については次のドキュメントを参照してほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.