連載
» 2013年04月25日 11時41分 UPDATE

WinRT/Metro TIPS:デザイン画面でデータをバインドするには?[Win 8/WP 8]

XAMLコードだけでデータ・ソースのインスタンスを生成し、それをデータ・コンテキストに設定してVisual Studioのデザイン画面に表示する方法を説明する。

[山本康彦,BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 前回までで紹介したデータ・バインドの方法では、実際にどのような表示になるかは実行してみないと分からない。実行する前に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>

Win 8用のMainPage画面に定義したClockクラスのインスタンス(XAML)
ClockクラスのインスタンスをClockInstanceという名前で定義している(分かりやすいようにクラス名とインスタンスの名前を変えている)。 また、DateTimeToHhMmSsConverterとBooleanToVisibilityConverterは、Clockクラスのプロパティをバインドする際に利用するバリュー・コンバータだ(バリュー・コンバータの詳細および名前空間やリソースがプロジェクトで定義されていない場合の対応については「文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」を参照)。

 一方、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>

WP 8用のMainPage画面に定義したClockクラスのインスタンス(XAML)
WP 8用のアプリでは、ページの開始タグに名前空間の定義も追加する。ここでは「MetroTips034」という名前空間名になっているが、適宜変更してほしい。リソース定義がない場合は上記の定義を新たに記述しよう。

 続いて、上で定義したインスタンスをコントロールのデータ・コンテキストに設定する。これには、コントロールのDataContextプロパティを使う。例えば、TextBlockコントロールでは次のようにXAMLコードを記述する。

<TextBlock x:Name="textClock1" …… 省略 ……
           DataContext="{StaticResource ClockInstance}"
           Text="{Binding NowTime}"  />

TextBlockコントロールのデータ・コンテキストに上で定義したClockInstanceインスタンスを設定した(XAML)

 これで、ページの<Resources>要素内で定義したClockクラスのインスタンスがTextBlockコントロールのデータ・コンテキストに設定され、TextBlockコントロールのTextプロパティはそのインスタンスのNowTimeプロパティとバインドされる。以上はXAMLコードだけで記述されているので、次の画像のようにデザイン画面でも表示される。これで実際の画面表示がどうなるかをデザイン時に確認できるようになる。

VS 2012のデザイン画面にデータ・バインドの結果が表示されている様子(Win 8/WP 8)
VS 2012のデザイン画面にデータ・バインドの結果が表示されている様子(Win 8/WP 8) VS 2012のデザイン画面にデータ・バインドの結果が表示されている様子(Win 8/WP 8)
この画像では分からないが、デザイン画面でも時刻は変わっていく。

データ・コンテキストをまとめて設定するには?

 以上で実際の表示がどうなるかをデザイン時に確認できるようになったが、コントロールごとにデータ・コンテキストを記述するのは面倒だ。実は、データ・コンテキストは親のコントロールのものを引き継ぐという特徴がある*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コントロールだけに設定した(XAML)
これで、子に当たる全てのコントロールのデータ・コンテキストにClockクラスのインスタンスが設定される。
なお、TextBlockコントロールとEllipseコントロールにデータ・バインドしている部分の詳細については、「文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」を参照してほしい。

データ・コンテキストをページに設定するには?

 上の例ではStackPanelコントロールにデータ・コンテキストを設定したが、それならば、配置された全てのコントロールの親である、 ページそのものにデータ・コンテキストを設定してもよいだろう。

 ただし、ページの開始タグ内に記述してもうまくいかない。次のようにして、ページの<Resources>要素の直後に*2ページの<DataContext>要素を追加する。

  …… 省略 ……
</Page.Resources>
<Page.DataContext>
  <Binding Source="{StaticResource ClockInstance}"/>
</Page.DataContext>
  …… 省略 ……

ページのデータ・コンテキストに設定した(Win 8用XAML)
ページの開始タグ内に「DataContext="……"」という指定が自動生成されている場合は、あらかじめ削除しておくこと。

  …… 省略 ……
</phone:PhoneApplicationPage.Resources>
<phone:PhoneApplicationPage.DataContext>
  <Binding Source="{StaticResource ClockInstance}"/>
</phone:PhoneApplicationPage.DataContext>
  …… 省略 ……

ページのデータ・コンテキストに設定した(WP 8用XAML)

*2 実際には、ページの<Resources>要素の直後でなくても、ページの最初の子要素のレベル(=ページの<Resources>要素と同レベル)であれば、どこに記述してもよい。


 以上の設定を行えば、StackPanelコントロールに設定した「DataContext="{StaticResource ClockInstance}"」を削除しても、時間が表示されるはずだ。時間が表示されないときには、デザイン画面を一度閉じてから、再度オープンしてみよう。

デザイン時と実行時でデータ・コンテキストを使い分けるには?

 デザイン画面でデータをバインドする基本は以上であるが、3点ほど補足しておく。1点目は「デザイン時属性」(または「設計時属性」)だ。

 デザイン時と実行時で、データ・コンテキストに設定するインスタンスを使い分けたい場合がある。Clockクラスのようなシンプルなものでは必要ないだろうが、例えばデータベースのサービスからデータを取得するようなデータ・ソースの場合は、デザイン時にはダミーのデータを表示させ、実行時には取得してきたデータを表示させるようにしたいだろう。

 このようなときに使うのがデザイン時属性だ。デザイン時属性は「d:」プレフィックスを付けて表される。また、デザイン時属性は実行時には無視される。デザイン時属性がないときには、「d:」プレフィックスを持たない同名の属性がデザイン時/実行時の両者で使われる。

 そこで、例えば先ほどのページのデータ・コンテキストを、次のようにデザイン時属性に置き換えると、実行時には表示されなくなる。

<d:Page.DataContext>
  <Binding Source="{StaticResource ClockInstance}"/>
</d:Page.DataContext>

ページのデータ・コンテキストをデザイン時属性に置き換えた(XAML)
こうすると、デザイン時にはClockクラスのプロパティが表示されるが、実行時には表示されなくなる。WP 8でもデザイン時属性の設定は上記のコードで行う(「d: phone:PhoneApplicationPage.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

世界標準時を表すGmtClockクラスのインスタンスをページのデータ・コンテキストに実行時に設定するコード(上:C#、下:VB)
これはWindowsストア・アプリの場合。WP 8では、コンストラクタの末尾に記述する。

 これで、デザイン時には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>

ページのデータ・コンテキストに通常のDataContext属性とデザイン時属性の両方を指定した(Win 8用XAML)
こうすると、デザイン時にはClockクラスのプロパティが表示され、実行時にはGmtClockクラスのプロパティが表示される。
なお、WP 8では、「Page.Resources」タグが「phone:ApplicationPageResources」タグに、「Page.DataContext」タグが「phone:PhoneApplicationPage.DataContext」タグになる(「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>

ページのデータ・コンテキストと異なるデータ・コンテキストを一部のコントロールだけに設定した(Win 8用XAML)
「StackPanelTime2」という名前のStackPanelとその子に当たるコントロールには、GmtClockInstanceがデータ・コンテキストに設定される。それ以外のコントロールには、Page.DataContextで設定したClockInstanceがデータ・コンテキストに設定される。
なお、WP 8では、「Page.Resources」タグを「phone:PhoneApplicationPage.Resources」タグに、「Page.DataContext」タグを「phone:PhoneApplicationPage.DataContext」タグに、それぞれ読み替えてほしい。

 このときのデザイン画面は次の画像のようになる。

部分的に異なるデータ・コンテキストを設定した画面(Win 8/WP 8)
部分的に異なるデータ・コンテキストを設定した画面(Win 8/WP 8) 部分的に異なるデータ・コンテキストを設定した画面(Win 8/WP 8)
1つめの時刻にはページのデータ・コンテキストに設定したClockInstanceインスタンスのプロパティ(=日本標準時)、2つめにはStackPanelのデータ・コンテキストに設定したGmtClockInstanceインスタンスのプロパティ(=世界標準時)が表示されている。

 また、個別に設定を行う際にも実行時のデータ・コンテキストとデザイン時のデータ・コンテキストを別個に設定が可能だ。これについては公開するサンプルのMainPage.xamlファイルを参照してほしい。

デザイン時か実行時かを判別するには?

 最後に、データ・ソースのコードなどで、デザイン時と実行時でロジックを切り替えたいときがある。そのようなときに、どちらのモードで動作しているかを判別するためのプロパティが用意されている。

  • Windowsストア・アプリ
    − Windows.ApplicationModel名前空間のDesignModeクラスが公開しているDesignModeEnabledプロパティ
  • WP 8アプリ
    − System.ComponentModel名前空間のDesignerPropertiesクラスが公開しているIsInDesignToolプロパティ

まとめ

 ページの<Resources>要素にデータ・ソースのインスタンスを定義し、ページの<DataContext>要素にそのインスタンスをバインドすれば、ページ内の全てのコントロールのデータ・コンテキストにそれが設定される。必要ならば個別にデータ・コンテキストを設定することもできる。

 また、デザイン時と実行時でデータ・コンテキストを使い分けられるし、コード内でデザイン時と実行時の判別も可能だ。

 デザイン時のデータ・バインドの詳細については次のドキュメントを参照してほしい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。