コントロール同士をデータ・バインドするには?[Win 8/WP 8]WinRT/Metro TIPS

データ・バインドの仕組みを使うと、データ・クラスだけでなく、コントロール同士もバインドできる。その方法を説明する。

» 2013年05月09日 02時43分 公開
[山本康彦BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

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

連載目次

 前回までに紹介してきたデータ・バインドの例では、何らかのデータを表現するクラス(=データ・クラス)のプロパティをコントロールにバインドしていた。しかし、データ・バインドの仕組みを使うと、コントロール同士もバインドできるのである。そこで本稿では、コントロールとコントロールをバインドする方法を解説する。本稿のサンプルは「Windows Store app samples:MetroTips #35(Windows 8版)」と「Windows Store app samples:MetroTips #35(WP 8版)」からダウンロードできる。

 なお、掲載しているコードは特記なき場合はWindowsストア・アプリとWindows Phone 8(以降、WP 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を使用している。

 WP 8向けのアプリを開発するには、SLAT対応CPUを搭載したPC上の64bit版Win 8 Pro以上Windows Phone SDK 8.0(無償)が必要となる。

自分自身とバインドするには?

 テキスト・ブロックに同内容のツールチップを付ける例を考えてみよう。例えば、文字列がテキスト・ブロックに表示しきれないほど長くなって末尾が切れてしまうようなときは、次の画像のように同じ文字列をツールチップで提供するのがよいだろう。

部分的に異なるデータ・コンテキストを設定した画面(Win 8/WP 8) テキスト・ブロックに同内容のツールチップを設定した画面の部分(Win 8)
テキスト・ブロックに設定した文字列が長くなって末尾が切れてしまうと予想されるときは、このようにツールチップでも同じ文字列を提供するとよい。
なお、WP 8には現時点ではツールチップのコントロールが実装されていないので、ツールチップが必要ならばPopupコントロールなどを使って自作することになる。

 これを素直に実装すると、次のようなXAMLコードになるだろう。

<TextBlock ……省略……
  Text="WinRT/Metro TIPS #35 (CS) コントロールとデータ・バインドするには?"
  ToolTipService.ToolTip="WinRT/Metro TIPS #35 (CS) コントロールとデータ・バインドするには?"
  />

テキスト・ブロックのTextプロパティとToolTipService.ToolTipプロパティに同じ文字列を設定した(XAML)
これは分かりやすいが、コードビハインドから文字列を変更するときには、2箇所のプロパティを変更しなければならない。

 上記のコードは、次のようにして片方をデータ・バインドにできる。バインドする対象のオブジェクトとして自分自身を指定するには、「RelativeSource={RelativeSource Mode=Self}」というキーワードを使う。これは、バインドするデータのソースが相対指定であること(=「RelativeSource」)と、相対的に自分自身を指定する(=「Mode=Self」)という設定だ。

<TextBlock ……省略……
  Text="WinRT/Metro TIPS #35 (CS) コントロールとデータ・バインドするには?"
  ToolTipService.ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"
  />

テキスト・ブロックのToolTipService.ToolTipプロパティを自分自身のTextプロパティにバインドした(XAML)
このようにバインドしておけば、コードビハインドからTextプロパティを変更するだけでツールチップも自動的に変わる。

 この応用としては、例えば入力された文字列に応じた色を返すようなバリュー・コンバータ*1を作り、それを介してテキストボックスの前景色を自分のTextプロパティにバインドすることなどが考えられる。このようにちょっとした機能をコントロールに付加するために、自分自身のプロパティとのバインディングが利用できるのだ*2

 なお、WP 8にはツールチップのコントロールが実装されていないので上記のサンプル・コードは動作しないが、自分自身のプロパティにバインドする方法は同じである*3

*1 バリュー・コンバータについては「WinRT/Metro TIPS: 文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」を参照。
*2 複雑になってきたら、ユーザー・コントロールや継承したカスタム・コントロールを作成した方がよい。
*3 WP 8のテキスト・ブロックでは、バリュー・コンバータを介さずに自身のプロパティとバインドするよい例を思いつかないのだが、次のようにして可能であることは確認できる。
<TextBlock Text="Red"
Foreground="{Binding Text, RelativeSource={RelativeSource Self}}" />


ほかのコントロールにバインドするには?

 ほかのコントロールへのバインドを行う場合も、自分自身にバインドするのと同じように記述できる。ただし、バインドする対象となるコントロールには名前を付けておき、バインドする対象はその名前で指定する。

 ここでは、バリュー・コンバータも使ってみよう。数値の0〜255を無彩色のSolidColorBrushオブジェクト(Win 8ではWindows.UI.Xaml.Media名前空間/WP 8ではSystem.Windows.Media名前空間)に変換するバリュー・コンバータを作ると、次のコードのようになる。

 最初にWin 8用のコードを示す。

public sealed class NumberToNeutralcolorConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string language)
  {
    try
    {
      byte b = (byte)((double)value);
      return new Windows.UI.Xaml.Media.SolidColorBrush(
                                        Windows.UI.Color.FromArgb(255, b, b, b)
                                      );
    }
    catch { }
    return Windows.UI.Xaml.DependencyProperty.UnsetValue;
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    return Windows.UI.Xaml.DependencyProperty.UnsetValue;
  }
}

Public Class NumberToNeutralcolorConverter
  Implements IValueConverter

  Public Function Convert(value As Object, targetType As Type, parameter As Object,
                          language As String) As Object Implements IValueConverter.Convert
    Try
      Dim b As Byte = CByte(DirectCast(value, Double))
      Return New Windows.UI.Xaml.Media.SolidColorBrush(
                                        Windows.UI.Color.FromArgb(255, b, b, b)
                                      )
    Catch

    End Try
    Return Windows.UI.Xaml.DependencyProperty.UnsetValue
  End Function

  Public Function ConvertBack(value As Object, targetType As Type, parameter As Object,
                              language As String) As Object Implements IValueConverter.ConvertBack
    Return Windows.UI.Xaml.DependencyProperty.UnsetValue
  End Function
End Class

Win 8用の数値をSolidColorBrushオブジェクトに変換するバリュー・コンバータ(上:C#、下:VB)

 そして、次に示すのがWP 8用のコードだ。

public sealed class NumberToNeutralcolorConverter : System.Windows.Data.IValueConverter
{
  public object Convert(object value, Type targetType, object parameter,
                        System.Globalization.CultureInfo culture)
  {
    try
    {
      byte b = (byte)((double)value);
      return new System.Windows.Media.SolidColorBrush(
                              System.Windows.Media.Color.FromArgb(255, b, b, b)
                            );
    }
    catch { }
    return System.Windows.DependencyProperty.UnsetValue;
  }

  public object ConvertBack(object value, Type targetType, object parameter,
                            System.Globalization.CultureInfo culture)
  {
    return System.Windows.DependencyProperty.UnsetValue;
  }
}

Option Strict On

Public Class NumberToNeutralcolorConverter
  Implements System.Windows.Data.IValueConverter

  Public Function Convert(value As Object, targetType As Type, parameter As Object,
                          culture As Globalization.CultureInfo) As Object _
                        Implements System.Windows.Data.IValueConverter.Convert
    Try
      Dim b As Byte = CByte(DirectCast(value, Double))
      Return New System.Windows.Media.SolidColorBrush(
                              System.Windows.Media.Color.FromArgb(255, b, b, b)
                            )
    Catch

    End Try
    Return System.Windows.DependencyProperty.UnsetValue
  End Function

  Public Function ConvertBack(value As Object, targetType As Type, parameter As Object,
                              culture As Globalization.CultureInfo) As Object _
                            Implements System.Windows.Data.IValueConverter.ConvertBack
    Return System.Windows.DependencyProperty.UnsetValue
  End Function

End Class

WP 8用の数値をSolidColorBrushオブジェクトに変換するバリュー・コンバータ(上:C#、下:VB)

 このバリュー・コンバータを介して、次のようにスライダ・コントロールのValueプロパティをテキスト・ブロックの前景色や文字列にバインドできる。バインドする対象のコントロールを指定するためには「ElementName=slider1」という記述を使う。これは「バインド対象の要素名(=ElementName)は『slider1』である」という意味だ。要素名に指定できるのは、実行時に同じ画面に含まれているコントロールの名前である。省略部分やXAMLコードの全体の構成などは、「Sample Code - MSDN」上に公開するソース・コードを参照していただきたい。

<Grid Background="DarkOrchid">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" />
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <TextBlock Text="{Binding Value, ElementName=slider1}"
    Foreground="{Binding Value, Converter={StaticResource NumberToNeutralcolorConverter}, ElementName=slider1}"
    ……省略…… />
  <Slider x:Name="slider1" Minimum="0" Maximum="255" Grid.Column="1" ……省略…… />
</Grid>

スライダ・コントロールのValueプロパティをテキスト・ブロックの前景色と文字列にバインドした(XAML)
バリュー・コンバータのインスタンス宣言部分は省略している。詳しくは「WinRT/Metro TIPS: 文字列以外の値をコントロールにバインドするには?[Win 8/WP 8]」を参照してほしい。

 これを実行してみると、本記事の最後に掲載する画像「完成した画面(Win 8/WP 8)」の上部のようになる。スライダを動かすと、それにつれてテキスト・ブロックの背景色と文字列が変わっていく。

データ・テンプレート内からコントロールにバインドするには?

 ここまでは実行時の画面内でコントロール自身にバインドしたり、ほかのコントロールにバインドしたりする例を見てきた。それでは、ユーザー・コントロールやデータ・テンプレートなど、実行時の画面とは切り離してXAMLコードを記述する場合はどうだろうか? 記述するときに「見えていない」コントロールの名前を指定してデータ・バインドできるのだろうか?

 答えはイエスだ。実行時に同じ画面に存在するコントロールならば、名前を使ってバインドできる。ここでは、別途定義したデータ・テンプレートの中から、画面上のコントロールにバインドしてみよう。

 対象のコントロールには、先ほどと同じスライダ・コントロールを使おう。そして、次のようなリスト・コントロールとそのデータ・テンプレートを用意する。

 まず、Win 8用ListViewコントロールのXAMLコードを示す。これは画面定義ファイルの中に記述する。

<ListView ……省略……
  ItemTemplate="{StaticResource SampleTemplate1}"
  >
  <!-- あまり実用的ではないが、以下のようなデータの与え方が可能だ -->
  <x:String>項目 (1)</x:String>
  <x:String>項目 (2)</x:String>
  <x:String>項目 (3)</x:String>
</ListView>

画面定義ファイルに記述したWin 8用のListViewコントロール(XAML)
ItemTemplateプロパティに指定している「SampleTemplate1」は、後に示すデータ・テンプレートである。

 WP 8のListBoxコントロールでは、上記のWin 8用のようなデータの与え方はできない*4ので、まずサンプル・データを提供するクラスを次のように作る。

*4 単に表示させるだけならListBoxItemクラスが使えるが、データ・テンプレートは利用できない。


public class SampleData : List<string>
{
  public SampleData()
  {
    this.Add("項目 (1)");
    this.Add("項目 (2)");
    this.Add("項目 (3)");
  }
}

Public Class SampleData
  Inherits List(Of String)

  Public Sub New()
    Me.Add("項目 (1)")
    Me.Add("項目 (2)")
    Me.Add("項目 (3)")
  End Sub
End Class

WP 8用のサンプル・データを提供するクラス(上:C#、下:VB)

 すると、WP 8用のListBoxコントロールのXAMLコードは次のように書ける。画面のリソースにSampleDataクラスのインスタンスを定義して、それを「ItemsSource=…」という属性でListBoxコントロールに与えるという記述は見慣れないかもしれないが、やっているのは前述のWin 8用のXAMLコードと同じことである。

<phone:PhoneApplicationPage
  xmlns:local="clr-namespace:MetroTips035CS"
  ……省略……
  >
  <phone:PhoneApplicationPage.Resources>
    ……省略……
    <local:SampleData x:Key="SampleData" />
  </phone:PhoneApplicationPage.Resources>

  ……省略……

        <ListBox ……省略……
                 ItemsSource="{StaticResource SampleData}"
                 ItemTemplate="{StaticResource SampleTemplate1}">
        </ListBox>

  ……省略……

画面定義ファイルに記述したWP 8用のListBoxコントロール(XAML)
ItemTemplateプロパティに指定している「SampleTemplate1」は、次に示すデータ・テンプレートである。

 次に、データ・テンプレートを示す。今回は、画面定義ファイルではなく、App.xamlファイルの中に記述する。記述しているときには、実際にテンプレートが適用されたときの画面にどんなコントロールが存在するかは分からないが、「slider1」という名前のコントロールにバインドする指定をした。

 データ・テンプレート内のテキスト・ブロックのTextプロパティには、テンプレートが適用されたアイテム(=前述のSampleDataクラスの各項目)をバインドする。「"{Binding}"」とだけ記述して、バインドするプロパティを指定しなかった場合は、データ・コンテキストで割り当てられたオブジェクトそのもの(=ここではテンプレートが適用されたアイテム、すなわち、前述のSampleDataクラスの各項目)がバインドされる。

 データ・テンプレート内のコンテナ・コントロール(=GridコントロールやFrameコントロール)のWidthプロパティとBackgroundプロパティには、前に説明したスライダ・コントロール「slider1」のValueオブジェクトをバインドする。Backgroundプロパティにバインドする際には、前述のバリュー・コンバータを使う。

 まずWin 8用のXAMLコードを示す。

<Application
  ……省略……
  xmlns:local="using:MetroTips035CS">
  <Application.Resources>
    <ResourceDictionary>
      ……省略……

      <local:NumberToNeutralcolorConverter x:Key="NumberToNeutralcolorConverter" />
      <DataTemplate x:Key="SampleTemplate1">
        <Frame Width="{Binding Value, ElementName=slider1}"
          Background="{Binding Value, Converter={StaticResource NumberToNeutralcolorConverter}, ElementName=slider1}" >
          <TextBlock Text="{Binding}" ……省略…… />
        </Frame>
      </DataTemplate>
    </ResourceDictionary>
  </Application.Resources>
</Application>

Win 8用のApp.xamlファイルに記述したデータ・テンプレート(XAML)
テキスト・ブロックを囲むFrameコントロールのWidthプロパティとBackgroundプロパティを、「slider1」という名前のコントロールのValueプロパティにバインドした。テキスト・ブロックのTextプロパティは、データ・コンテキストにバインドした。

 次がWP 8用のXAMLコードだ。

<Application
  x:Class="MetroTips035CS.App"
  ……省略……
  >

  <!--アプリケーション リソース-->
  <Application.Resources>
    ……省略……

    <local:NumberToNeutralcolorConverter x:Key="NumberToNeutralcolorConverter" />
    <DataTemplate x:Key="SampleTemplate1">
      <Grid Width="{Binding Value, ElementName=slider1}"
          Background="{Binding Value, Converter={StaticResource NumberToNeutralcolorConverter}, ElementName=slider1}" >
        <TextBlock Text="{Binding}" ……省略…… />
      </Grid>
    </DataTemplate>
  </Application.Resources>

  ……省略……
</Application>

WP 8用のApp.xamlファイルに記述したデータ・テンプレート(XAML)
テキスト・ブロックを囲むGridコントロールのWidthプロパティとBackgroundプロパティを、「slider1」という名前のコントロールのValueプロパティにバインドした。テキスト・ブロックのTextプロパティは、データ・コンテキストにバインドした。

 このように、存在するかどうか分からないコントロールの名前を指定してバインドしても、エラーにならずコンパイルできる。

 実行してみると、次の画像のように、スライダを動かすにつれてListViewコントロールの各項目の幅と背景色が変化する。

部分的に異なるデータ・コンテキストを設定した画面(Win 8)1
部分的に異なるデータ・コンテキストを設定した画面(Win 8)2
部分的に異なるデータ・コンテキストを設定した画面(WP 8)1
部分的に異なるデータ・コンテキストを設定した画面(WP 8)2 完成した画面(Win 8/WP 8)
スライダ・コントロールを動かすと、スライダ・コントロールの左側のテキスト・ブロックの文字列とその前景色、および、ListViewコントロールの各項目の幅と背景色が変化する。

 ここで、画面に存在しない名前を指定してしまった場合を試しておこう。先ほどのデータ・テンプレート内の「slider1」というコントロール名を、存在しない名前(例えば「slider2」など)に書き換えてみてほしい。しかし、コンパイル時にも実行時にもエラーにはならない(もちろん、スライダ・コントロールを動かしてもListViewコントロールの項目の幅や背景色が変化することはない)。

 データ・バインドでは、指定を間違えてもエラーにはならず、実行時には既定値が使用されるのだ。そのような緩やかなルールになっているからこそ、この例のように別の場所で定義したデータ・テンプレートからも画面にあるコントロールにバインドできるのである*5

*5 ただし、可能であるからといって乱用すると、データ・テンプレートの動作が分かりにくくなり混乱を招くことになる。実際の開発では、データ・テンプレートがバインドする対象はデータ・コンテキストだけにしておくことを推奨する。上の例では、スライダの値と双方向バインドするデータ・クラスを用意し、データ・テンプレートもそのデータ・クラスにバインドされるように実装するとよい。


まとめ

 コントロールのプロパティを、ほかのコントロールや自分自身のプロパティにデータ・バインドできる。複数のコントロールの動作を連携できるのはとても便利なので、ぜひマスターしてほしい。ただし、複雑になってきたときには、ユーザー・コントロールやカスタム・コントロールを作ったり、バインドするデータ・クラスを見直したりすることで複雑さを低減できることも一緒に覚えておいてほしい。

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

WinRT/Metro TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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