• 検索結果がありません。

グラフィックス 目次

N/A
N/A
Protected

Academic year: 2021

シェア "グラフィックス 目次"

Copied!
56
0
0

読み込み中.... (全文を見る)

全文

(1)

■ 第4 回 WPF の「リソース、スタイル、テンプレート」を習得しよう ■ 前回説明した依存関係プロパティは、「ほかの要素の値に依存してプロパティの値を決定する機構」と いえる。WPF ではこの仕組みを基軸として、リソース、スタイル、コントロール・テンプレートなど の高度な機能を提供している。 特にWPF の柔軟性を象徴する機能がコントロール・テンプレートで、この機能を用いることでコン トロールの外観を自由自在にカスタマイズ可能となる。Windows フォームなどの既存の GUI 作成フレ ームワークでは、コントロールに対して背景色やフォント・サイズの変更など、限定的なカスタマイズ しかできなかった。これに対して、WPF のコントロール・テンプレートは、外観をほぼ無制限にカス タマイズできるポテンシャルを秘めている。 今回は、これらのWPF の高度な機能について説明していく。 ■ リソース WPF では、複数の UI 要素で 1 つのオブジェクトを共有するために、リソースという仕組みを持ってい る。例として、背景色を指定するためのブラシ(具体的には<SolidColorBrush>要素)を 2 つの<Button> 要素で共有するXAML コードを List 1 に示す。この XAML コードの表示結果は Figure 1 のようにな る。

<Window x:Class="WpfApplication1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="MainWindow" Height="100" Width="200"> <Window.Resources>

<SolidColorBrush x:Key="MyBrush" Color="Blue"/> </Window.Resources> <StackPanel> <Button Content="Button 1" Background="{StaticResource MyBrush}"/> <Button Content="Button 2" Background="{StaticResource MyBrush}"/> </StackPanel> </Window> List 1: リソースによるブラシの共有

W

WP

PF

F

入門

(2)

Figure 1: List 1 の XAML コードの表示結果 このように、リソースを用いてオブジェクトを共有することで、インスタンス生成などのオーバーヘッ ドを削減できる。上述の例では、青色のブラシを1 つだけ生成して共有しており、2 つの<Button>要素 でそれぞれ個別にブラシを作るよりも生成されるインスタンスが少ない。 また、リソースは、UI 要素に対する設定を一カ所に集めることで、保守を容易にする。上述の例でい うと、リソース中で定義されたブラシを変更するだけで、これを参照する 2 つの<Button>要素の背景 色を一斉に変更できる。 2 種類の「リソース」 「リソース」という言葉が混乱を招く場合もあるので、ここで補足的な説明を入れておく。 Windows アプリケーションでは、実行可能ファイル(=.EXE ファイル)などのアセンブリの中に画像 などのバイナリ・ファイルを埋め込むための「リソース」機構を持っている。この仕組みは、WPF ア プリケーションでももちろん利用できる。この機構と、本稿で説明するWPF のリソース機構を区別す る際には、以下のように呼び変える。 ・アセンブリ・リソース: アセンブリの中にバイナリ・ファイルを埋め込むためのリソース機構。バ イナリ・リソースなどとも呼ぶ。 ・オブジェクト・リソース: 本稿で説明する、.NET オブジェクトを複数の UI 要素から参照するため のリソース機構。本稿では、単に「リソース」と呼ぶ場合には、こちらを指すものとする 。 リソース定義 List 1 でも示したように、リソースは<Window>などの要素(正確には、FrameworkElement 型を継 承する要素)のResources プロパティ内に定義する。Resources プロパティは ResourceDictionary 型 (System.Windows 名前空間)で、この型はキーも値も object 型の辞書となっている。

連載第2 回で説明したように、XAML コード中で IDictionary インターフェイスを実装する要素に子要 素 を 追 加 す る 際 に は x:Key XML 属性の指定が必須となる。ただし、後述する Style クラス (System.Windows 名 前 空 間 ) の よ う に 、( ク ラ ス に 対 し て ) DictionaryKeyProperty 属 性 (DictionaryKeyPropertyAttribute)を付与して、(x:Key XML 属性の)代替となるプロパティ(Style クラスの場合はTargetType プロパティ)を設定しているクラスの場合には、x:Key XML 属性 の指定 を省略可能である。

例えばList 2 で示している Style クラスでは、x:Key XML 属性の代替として TargetType プロパティを 利用している(<Style>要素の TargetType プロパティについては、後述のスタイルの節で詳しく説明す る)。

(3)

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Window.Resources>

<!-- x:Key の指定が必須 -->

<SolidColorBrush x:Key="Brush1" Color="Green" /> <!-- TargetType が x:Key の代わりに利用される --> <Style TargetType="Button">

<Setter Property="Background" Value="LightBlue" /> </Style> </Window.Resources> <Grid> <Button Content="ボタン"/> </Grid> </Window> List 2: リソースの定義例 外部リソースの取り込み リソースは<Windows.Resources>タグの中に直接定義する以外に、ルート要素が ResourceDictionary 型のXAML ファイルを別途用意して、この XAML ファイルを取り込む形で利用することもできる(外 部リソース)。

外部リソースの定義例をList 3 に示す。ここでは、この XAML ファイルに Styles.xaml という名前を 付けて保存するものとする。

<ResourceDictionary

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Style TargetType="Button">

<Setter Property="Background" Value="LightBlue" /> </Style>

</ResourceDictionary>

List 3: 外部リソースの定義例(Styles.xaml)

利用側が外部リソースを取り込むには、List 4 に示すように、<ResourceDictionary>要素に Source 属 性を付けることで行う。

<Window x:Class=" WpfApplication1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

(4)

<Window.Resources> <ResourceDictionary Source="Styles.xaml"/> </Window.Resources> <Grid> <Button Content="ボタン"/> </Grid> </Window> List 4: 外部リソースの取り込み例 また、ResourceDictionary.MergedDictionaries プロパティを用いることで、複数のリソース・ディク ショナリを1 つに結合することもできる。 例えば、Styles.xaml に加えてもう 1 つ、Brushes.xaml という名前の外部リソースを用意したものとし て、これらの外部リソース両方を取り込むためには、List 5 に示すような XAML コードを記述する。 この例では、外部リソースに加えて、さらにローカルでリソース・ディクショナリを結合している。 <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Styles.xaml"/> <ResourceDictionary Source="Brushes.xaml"/> <ResourceDictionary>

<SolidColorBrush x:Key="LocalResource" Color="Red" /> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> List 5: 複数のリソース・ディクショナリの結合 ちなみに、通常、リソースはインスタンスが1 つだけ生成され、そのただ 1 つのインスタンスが複数の UI 要素から参照されることになる。

この挙動を変更したい場合、List 6 に示すように、x:Shared XML 属性を false に設定することで、リ ソースが参照されるたびに別のインスタンスが生成されるようにすることも可能である。

<Grid.Resources>

<SolidColorBrush x:Shared="false" x:Key="Brush1" Color="Blue" /> </Grid.Resources>

<Button Content="ボタン 1" Background="{StaticResource Brush1}" /> <Button Content="ボタン 2" Background="{StaticResource Brush1}" /> List 6: リソースのインスタンスを毎度生成する例

リソース利用

リソースを参照するためには、StaticResource マークアップ拡張もしくは DynamicResource マークア ップ拡張を利用する。実行時に値を変化させる必要のないものにはStaticeResource マークアップ拡張

(5)

を用いる。一方、DynamicResource は、実行時に値が変化し、その変化を参照している要素に反映さ せる必要がある場合に用いる。

リソースの検索は、親要素を下から上に階層的にたどって行われる。 List 7 に示すように、同名のキ ーを持つリソース・ディクショナリがあった場合、Figure 2 に示すように、内側のスコープ(階層的 に下)にあるリソースが優先的に利用される。

<Window x:Class=" WpfApplication1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Window.Resources>

<SolidColorBrush x:Key="Brush1" Color="Red" /> <SolidColorBrush x:Key="Brush2" Color="Red" /> </Window.Resources>

<StackPanel>

<StackPanel.Resources>

<!-- 内側のリソースを優先的に利用するため、 ボタン 2 にはこちらのブラシが反映される --> <SolidColorBrush x:Key="Brush2" Color="Blue" /> </StackPanel.Resources> <Button Content="ボタン 1" Background="{StaticResource Brush1}"/> <Button Content="ボタン 2" Background="{StaticResource Brush2}"/> </StackPanel> </Window> List 7: リソースの利用例(リソース検索の優先度)

(6)

Figure 2: リソース参照の仕組み システム・リソース XAML コードの中で定義したリソースに加えて、「個人設定」などで設定されたシステム色やフォント などの、システム・リソースも利用できる。システム・リソースを利用するには以下のクラス(いずれ もSystem.Windows 名前空間)を使用する。 ・SystemColors: システム色やブラシを取得する。 ・SystemFonts: システム・フォントを取得する。 ・SystemParameters: ウィンドウの境界線やキャプションの幅などのシステム設定を取得する。 システム・リソースは、SystemColors.DesktopBrush などの静的プロパティを通して直接取得するこ ともできるが、これを StaticResource マークアップ拡張などでリソース参照するためのキーとして、 SystemColors.DesktopBrushKey などの静的プロパティが用意されている。 例えば、List 8 に示すような XAML コードでデスクトップの背景色を利用できる。 <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="MainWindow" Width="200" Height="200"> <Grid Background=

"{DynamicResource {x:Static SystemColors.DesktopBrushKey}}"> </Grid> </Window> List 8: システム・リソースの利用例(デスクトップの背景色の取得) この例ではDynamicResource マークアップ拡張を利用しているが、こうすることで、アプリケーショ ン起動中にエンド・ユーザーがデスクトップの背景色を変更した際に、その変更がアプリケーションに 即座に反映される。 例えば、List 8 の表示結果は Figure 3 のようになる。この例では、デスクトップの背景色を灰色からピ ンクに変更している。

(7)

Figure 3: List 8 の表示結果(システム設定の変更を即座に反映) ■ スタイル WPF は、HTML でいうところの CSS のようなスタイル設定の機構を持っている。CSS 同様、WPF で もスタイルを用いることで、UI 要素の外観をカスタマイズ可能である。 スタイルの定義 WPF のスタイルは、List 9 に示すように、<Setter>要素(=プロパティの値を設定するための要素) のリストとして定義する。

<Style>要素の TargetType プロパティにはスタイルを適用したい型の名前を指定する。また、<Setter> 要素のProperty プロパティおよび Value プロパティに、それぞれ対象とするプロパティ名と値を指定 する。

<Style TargetType="Button">

<Setter Property="Background" Value="DarkSeaGreen" /> <Setter Property="Foreground" Value="LightPink" /> </Style>

List 9: スタイルの定義例

これまでのXAML エディタでは、<Setter>要素に対する IntelliSense が効かず、プロパティ名を覚え ていなければスタイル設定ができないという、もどかしい状態が続いていた。しかし、最新のSilverlight 4 Tools for Visual Studio 2010 をインストールすることで、WPF の XAML エディタも同時に更新され、 Figure 4 に示すように、<Style>要素に対する IntelliSense が有効になる。(Visual Studio 2010 から、 WPF と Silverlight の XAML エディタが共通化された。Silverlight Tools の更新で WPF も同時に更新 されるのはこのためである)。

(8)

Figure 4: <Style>要素に対する IntelliSense スタイルの適用

FrameworkElement クラス(System.Windows 名前空間)には Style プロパティがあり、この Style プロパティに値を設定することで、定義したスタイルを適用できる。 スタイルは、FrameworkElement クラスの Style プロパティに対して直接記述することもできるが、 通常は、本稿の前半で解説したリソースの中で定義して利用する。スタイルとリソースは非常に相性が よく、リソースの中でスタイルを定義することで、複数のUI 要素にスタイルを一斉適用可能である。 特に、リソース中にx:Key XML 属性を指定しないスタイルを定義することで、TargetType プロパティ で指定した型の要素すべてに自動的にスタイルが適用される。 この3 種類のスタイル利用(自動適用、明示的なリソース指定、直接記述)の例を List 10 に、また、 その表示結果をFigure 5 に示す。 <StackPanel> <StackPanel.Resources> <!-- x:Key なしのスタイルを定義することで、 TargetType で指定した型すべてにスタイルを適用する --> <Style TargetType="Button">

<Setter Property="Background" Value="LightBlue" /> <Setter Property="Foreground" Value="Red" /> </Style>

<!-- x:Key の明示 -->

<Style x:Key="MyButtonStyle" TargetType="Button"> <Setter Property="Background" Value="DarkSeaGreen" /> <Setter Property="Foreground" Value="LightPink" /> </Style> </StackPanel.Resources> <!-- スタイルの自動適用 --> <Button Content="ボタン 1" /> <!-- x:Key を指定して明示的にスタイルを適用 --> <Button Style="{StaticResource MyButtonStyle}" Content="ボタン 2" />

<!-- スタイルを直接記述 --> <Button Content="ボタン 3"> <Button.Style>

<Style TargetType="Button">

<Setter Property="Background" Value="Gray" /> </Style>

(9)

</Button> </StackPanel> List 10: スタイルの利用例 Figure 5: List 10 の表示結果 スタイルの継承 <Style>要素は、BasedOn プロパティを指定することで、ほかのスタイルを継承できる。この仕組みに より、スタイルを部分的に書き換えたり、<Setter>要素を追加したりといったことが可能だ。 List 11 にスタイルの継承例を、また、Figure 6 にその表示結果を示す。この例では、背景色および前 景色を指定するスタイルを継承し、さらにフォント・サイズを指定するスタイルを定義している。 <StackPanel Width="90"> <StackPanel.Resources> <Style TargetType="Button">

<Setter Property="Background" Value="LightBlue" /> <Setter Property="Foreground" Value="Red" /> </Style>

<!-- 自動適用版のスタイルを基にして、新たにスタイルを作成 --> <Style x:Key="MyButtonStyle" TargetType="Button"

BasedOn="{StaticResource {x:Type Button}}"> <Setter Property="FontSize" Value="20" />

</Style>

</StackPanel.Resources> <Button Content="ボタン 1" />

<Button Style="{StaticResource MyButtonStyle}" Content="ボタン 2" /> </StackPanel> List 11: スタイルの継承の利用例 Figure 6: List 11 の表示結果 トリガー スタイルの作成では、特定の条件下でのみ働くスタイルを定義したいという場面がしばしば出てくる。

(10)

例えば、「マウス・カーソルが上に乗っているときや、クリックされたときだけスタイルを変えたい」 というような要求は必ずといっていいほど出るだろう。このような要求に応えるため、WPF のスタイ ルではトリガーという仕組みを持っている。 具体的には、<Style>要素の Triggers プロパティを設定することで、特定の条件下でのみ働くスタイル を定義することができる。例えば、List 12 のような XAML コードにより、マウス・カーソルが上に乗 ったときだけ水色に、フォーカスを得ているときだけピンク色に変化するテキストボックスが得られる。 <StackPanel Width="60"> <StackPanel.Resources> <Style TargetType="TextBox">

<Setter Property="Background" Value="LightGray" /> <Style.Triggers>

<Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="LightBlue" /> </Trigger>

<Trigger Property="IsFocused" Value="True">

<Setter Property="Background" Value="LightPink" /> </Trigger> </Style.Triggers> </Style> </StackPanel.Resources> <TextBox Text="テキスト" /> </StackPanel> List 12: トリガーを用いた条件付きのスタイル適用例 <Style.Triggers>要素中に記述できるのは、Trigger クラスだけではなく、以下のようなものがある(い ずれもSystem.Windows 名前空間中のクラス)。 ・Trigger: 特定のプロパティの値の変化をトリガーとして、Setter を用いてプロパティ値を変更する。 ・MultiTrigger: Trigger を複数条件に対応させたもの。指定したすべての条件が満たされた場合にト リガーがかかる。 ・DataTrigger: スタイル適用先の UI 要素だけでなく、データ・バインディングされたデータを監視 する。 ・MultiDataTrigger: DataTrigger の複数条件版。 ・EventTrigger: プロパティ値の変化ではなく、イベントの発生をトリガーとする。また、Setter で はなくストーリーボードを使ったアニメーションによりプロパティ値を変化させる。 データ・バインディングやアニメーションに関しては次回以降で説明する。EventTrigger に関しても、 アニメーションの回にあらためて説明を行う。 また、WPF 4 では、状態の変化に応じたスタイルの変更を管理するために、新たに VisualStateManager というクラスが追加された。このVisualStateManager クラスは、もともとは Silverlight で実装され たものだが、.NET Framework 4 で WPF にも輸入されることになった。 VisualStateManager クラスの利用にはアニメーションの知識が必要なため、Trigger クラスを利用す

(11)

るより少し難易度は高い が*1、状態の管理が行いやすく、可能ならばこちらを用いた方がいいだろう。 VisualStateManager クラスに関しても、アニメーションの回で説明する予定である。

*1 Visual Studio の XAML エディタにはアニメーション作成をサポートする機能がなく、複雑なアニメ ーションの作成が必要な場合にはExpression Blend の利用を考えてみるべきであろう。 ■ コントロール・テンプレート スタイルを用いることで、コントロールなどの外観をカスタマイズできるが、前節で説明したような単 純なプロパティ値の変更だけでは、カスタマイズ可能な範囲は極めて限られている。背景色などの変更 程度であれば、従来のGUI 作成フレームワークでもある程度可能な範囲である。WPF のスタイルには、 トリガーを用いた「状態の変化に応じたスタイル適用」などの先進的な機能もあるものの、まだ驚く範 囲ではないだろう。 Windows フォームなど、従来の GUI 作成フレームワークでは、出来合いのコントロールを用いること で高い生産性を得ていたが、その半面、カスタマイズ性は非常に限られていた。結果として、大幅なカ スタマイズが必要な場合には、新たなコントロールを1 から自作する必要があった。この場合、外観の 新規作成だけでなく、例えば「ボタンのクリック」や「テキストの選択」などの機能面まで含めて作り 直す必要があり、非常に負荷の高い作業となる。 これに対して、WPF では、コントロール・テンプレートという機能を用いることで、「ボタンのクリッ ク」などの機能を残したまま、外観だけを任意に変更可能である。 コントロール・テンプレートの利用 まず、最低限のコントロール・テンプレートを見てみよう。 List 13 に、ボタンの外観を水色のだ円形に変えるテンプレートを示す。ボタンなどのコントロールの 基底となるControl クラス(System.Windows.Controls 名前空間)は Template というプロパティを持 っていて、このTemplate プロパティに ControlTemplate クラス(System.Windows.Controls 名前空 間)のインスタンスを設定すればよい。

<Button>

<Button.Template>

<ControlTemplate TargetType="Button">

<Ellipse Fill="LightBlue" Width="80" Height="30"/> </ControlTemplate> </Button.Template> </Button> List 13: ボタンの外観を水色のだ円形に変えるテンプレート この程度の例であれば、単純にだ円(=<Ellipse>要素)に Mouse.MouseDown イベントを追加した方 が似たようなことを手早く実現できるかもしれない。ただ、Button クラスなどのコントロールの場合、 次回以降で説明する「コマンド」という仕組みも持っているため、これを利用したければ、やはりコン トロール・テンプレートを使うことになるだろう。 Button クラスなどのコントロールや、Ellipse クラスなどの「シェイプ」に関する詳細は次回以降で説 明する。

(12)

コントロール・テンプレートのリソース化と自動適用

もちろん、コントロール・テンプレートもリソース化可能である。List 14 に、コントロール・テンプ レートをリソース化する例を示す。

<StackPanel>

<StackPanel.Resources>

<ControlTemplate x:Key="MyButtonTemplate" TargetType="Button"> <Ellipse Fill="LightBlue" Width="80" Height="30"/>

</ControlTemplate> </StackPanel.Resources>

<Button Template="{StaticResource MyButtonTemplate}" /> </StackPanel> List 14: コントロール・テンプレートのリソース化の例 また、スタイルと組み合わせることで、コントロール・テンプレートの自動適用も可能だ。List 15 に その例を示す。 <StackPanel> <StackPanel.Resources> <!-- 自動適用スタイル --> <Style TargetType="Button"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button">

<Ellipse Fill="LightBlue" Width="80" Height="30"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </StackPanel.Resources> <!-- 以下のボタンには、 自動的にコントロール・テンプレートが設定される --> <Button /> </StackPanel> List 15: コントロール・テンプレートの自動適用例 ContentPresenter と TemplateBinding それでは、もう少し複雑な例を見てみよう。 前述の最低限の例では、<Button>要素の Content プロパティを設定してもボタンの中身が表示されな くなってしまうという問題がある。また、だ円形の色も水色に固定されてしまっている。そこで、List 16

(13)

に示すような修正が必要となる。 <StackPanel Width="80"> <StackPanel.Resources> <Style TargetType="Button"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Grid>

<Ellipse Fill="{TemplateBinding Background}"/> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </StackPanel.Resources>

<Button Content="ボタン 1" Background="LightBlue" Height="30" /> <Button Content="ボタン 2" Background="LightPink" Height="30" />

</StackPanel>

List 16: ContentPresenter と TemplateBinding 以下の2 つがポイントとなる。 ・<ContentPresenter>要素: この要素が置かれた位置にコントロールの中身(=Content プロパティ に与えた値)が配置される。 ・TemplateBinding マークアップ拡張: コントロール・テンプレートの適用先のコントロールに与え られたプロパティ値を取得するために利用する。 上記のコード例では、<ContentPresenter>要素により、<Grid>要素内の水平/垂直方向の中央に <Button>要素の Content プロパティの値(具体的には「ボタン 1」と「ボタン 2」)が表示され、さら にTemplateBinding マークアップ拡張によって、コントロール・テンプレートの適用先である<Button> 要素のBackground プロパティの値(具体的には「LightBlue」と「LightPink」)が取得されて用いら れる。Figure 7 に、その表示結果を示す。 Figure 7: List 16 の表示結果 ルーティング・コマンド スクロールバーのように、挙動が少し複雑なコントロールに対してコントロール・テンプレートを適用

(14)

する場合、「ルーティング・コマンド」という仕組みを利用することになる。ルーティング・コマンド の詳細については次回以降で説明することになるが、簡単にいうと、「ページ・アップ」や「端までス クロール」などといった操作が発生したことを親要素に伝達するための仕組みである。

ここでは参考程度のコード提示にとどめるが、ScrollBar クラスに対してコントロール・テンプレート を適用する例をList 17 に示す。

<Grid Width="150" Height="150"> <Grid.Resources> <ControlTemplate x:Key="VerticalScrollBarTemplate" TargetType="ScrollBar"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="15"/> <RowDefinition Height="*"/> <RowDefinition Height="15"/> </Grid.RowDefinitions> <RepeatButton Command="ScrollBar.LineUpCommand" Background="LightBlue" />

<Track Grid.Row="1" IsDirectionReversed="True"> <Track.DecreaseRepeatButton> <RepeatButton Command="ScrollBar.PageUpCommand" Background="Red" /> </Track.DecreaseRepeatButton> <Track.Thumb> <Thumb Background="Blue" /> </Track.Thumb> <Track.IncreaseRepeatButton> <RepeatButton Command="ScrollBar.PageDownCommand" Background="Green" /> </Track.IncreaseRepeatButton> </Track> <RepeatButton Command="ScrollBar.LineDownCommand" Background="LightPink" Grid.Row="2" /> </Grid> </ControlTemplate> <Style TargetType="ScrollBar"> <Style.Triggers>

<Trigger Property="Orientation" Value="Vertical"> <Setter Property="Template" Value="{StaticResource VerticalScrollBarTemplate}" /> </Trigger> </Style.Triggers> </Style> </Grid.Resources> <ScrollViewer HorizontalScrollBarVisibility="Visible">

(15)

<Ellipse Width="500" Height="500" Fill="Gray" /> </ScrollViewer> </Grid> List 17: ScrollBar へのコントロール・テンプレートの適用例 WPF Themes(WPF テーマ) コントロール・テンプレートの仕組みは高機能で、柔軟なカスタマイズが可能ではあるが、これをすべ てのコントロールに対して1 から定義するのは非常に大変な作業となる。自作ではなく、出来合いのも のをどこかから探してきて利用する方が現実的かもしれない。 ありがたいことに、Codeplex においてフリーの WPF テーマが公開されている。コントロール・テンプ レートの利用の際には参考にしてみるのもいいだろう。 ・WPF Themes 次回はデータ・バインディングの仕組みや具体的な用途について説明を行っていく。 ■ 第5 回 WPF の「データ・バインディング」を理解する ■ 今回および次回の2 回に渡り、ビューとモデルの疎結合を実現するための仕組みとして、データ・バイ ンディングとコマンドという 2 つの機能について説明する。まず今回は、これらの機能の背景にある GUI アプリケーションに対する要件と、データ・バインディングについて説明を行っていく。 ■ GUI アプリケーションに対する要件 WPF のデータ・バインディングやコマンドといった仕組みを説明する前に、そもそも GUI アプリケー ションに対して、どのような要件があるのかを整理してみよう。ここでは、実装上で満たすべき要件と して「ビューとモデルの疎結合」と、GUI アプリケーションに求められる機能(の中で、今回はデータ・ バインディングに関係する部分)を紹介する。 ビューとモデルの疎結合 GUI アプリケーション開発においてよくいわれる言葉として、「ビュー(view)とモデル(model)を 疎結合にしろ」というものがある。 すなわち、アプリケーションの見た目にかかわる部分(=ビュー)と、見た目と関係なく成り立つロジ ックや表示したいデータ(=モデル)を分けるのが良いとされる。オブジェクト指向言語の場合には、 ビューとモデルを別クラスとして表現し、互いのクラスに参照関係を作らないことを指す(そうするこ とで、再利用性やテスト可能性が向上する)。 単に「モデル」というと意味が広すぎるが、要は、業務を分析し、モデル化して得られたデータ(=ド メイン・モデル)を指す言葉だ。アプリケーションの種類によっては、モデルという言葉がしっくりこ ないかもしれないが、(GUI であろうと CUI(character-based user interface)であろうと)表示や操 作の方法にかかわらず変わらない部分を独立させ、その部分を指して「モデル」と呼んでいると思えば いいだろう。

(16)

データ・バインディングと関連して GUI アプリケーションに求められる機能としては、以下のような ものがある。 ○同じデータを複数のウィンドウやコントロールから参照 通常、モデルの持っているデータをビュー上に表示する方法は1 通りではない。例えば、同じデータを、 テーブルに表示したい場合もあれば、折れ線グラフなどで表示したい場合もあるだろう。また、テーブ ルとグラフを同時に表示したうえで、テーブル上での値の変更が即座にグラフ側に反映されてほしい場 合も少なくないだろう。

Visual Studio を例にとって 1 つ具体例を挙げるなら、XAML エディタがまさにこのような挙動になっ ている。

Figure 1 に示すように、この XAML エディタは、マウスを使って視覚的にデザインするための「デザ イン」画面(画像上部)と、XAML コードを文字ベースで入力する「XAML」画面(画像下部)を持っ ている。そして、どちらか片方への変更があった場合、もう一方に即座に変更結果が反映される。

(17)

Figure 1: 同じデータを複数のコントロール上に表示する例(XAML エディタ) このような機能を実現するためには、データの変更を通知するような仕組みが必要で、このためにWPF は「データ・バインディング」という仕組みを持っている(また、次回説明する「コマンド」も、ビュ ーとモデルの疎結合を保つために有用である)。次節以降では、このデータ・バインディングについて 説明していく。 ■ データ・バインディング WPF では、データ・ソース(=モデルなどの、データの提供元)をビュー(=WPF の場合は XAML コード)上のUI 要素と簡単に結び付けるために、データ・バインディングという仕組みを提供してい る。「バインディング」(binding:結合)や「結び付ける」という言い方をしているが、これは、値を 一度だけ代入するのではなく、「1 カ所で値が変化するたびに、ほかの個所にもその変化が即座に伝搬さ れる」ということを指している。 具体的には、以下のような手順でデータ・ソースとUI 要素を結び付ける(Figure 2)。 ・XAML コード中に「このプロパティの値をここに表示する」という印だけを入れておく ・表示したいデータ・ソースは、DataContext プロパティを通じてビューに渡す Figure 2: データ・バインディングの利用方法 アプリケーションの要件によっては、モデルのインスタンスをそのまま DataContext プロパティに渡 す だ け で よ い 。 例 え ば 、 モ デ ル が 最 初 か ら WPF 向 け に 作 ら れ て い る 場 合 や 、 後 述 す る INotifyPropertyChanged インターフェイスなどの仕組みを必要としない単純な要件の場合などである。 ただし実際には、このような場合に該当することはあまりなく、WPF 向けにモデルをラッピングした もの(=ビュー・モデルと呼ぶ)を作って、これをDataContext プロパティに渡すことになる。 データ・バインディングの使い方 それでは、データ・バインディングの具体例を見てみよう。まず、List 1 に示すように、XAML コード 中にBinding マークアップ拡張を使って「このプロパティの値をここに表示する」という印を入れてお

(18)

く。

<Window x:Class="atmarkit05.MinimalSampleWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Width="100" SizeToContent="Height" Title="ウィンドウ名"> <StackPanel>

<TextBlock Text="{Binding X}" /> <TextBlock Text="{Binding Y}" /> </StackPanel>

</Window>

List 1: データ・バインディングの利用例(XAML)

<TextBlock>要素の Text プロパティに対して「{Binding X}」と書くことで、「Text プロパティの値と X プロパティの値を結び付ける」という意味になる。

次に、FrameworkElement クラス(System.Windows 名前空間)(Window クラスもこのクラスを継承 している)のDataContext プロパティにデータ・ソースを渡す。 今回はList 2 に示すように、分離コード中のビュー・クラス(Window クラスを継承)のコンストラク タで DataContext プロパティに匿名クラスを使って渡す(ただし、実際には、データの差し替えやテ ストのしやすさを考えると、ビュー・クラスの外でデータ・ソースを渡す方が好ましい)。実行結果は Figure 3 のようになる。 Visual Basic Namespace atmarkit05 Class MinimalSampleWindow Sub New() InitializeComponent()

Me.DataContext = New With {.X = 10, .Y = 20} End Sub End Class End Namespace Visual C# using System.Windows; namespace atmarkit05 {

public partial class MinimalSampleWindow : Window { public MinimalSampleWindow() { InitializeComponent(); this.DataContext = new { X = 10, Y = 20 }; }

(19)

} } List 2: データ・バインディングの利用例(分離コード) Figure 3: List 1 の表示結果 こ の よ う に 、 単 に デ ー タ を 表 示 す る だ け な ら 何 も 特 殊 な 実 装 は 必 要 な い ( 後 述 す る よ う な INotifyPropertyChanged インターフェイスの実装は不要)。また、同名のプロパティさえ持っていれば よく、具体的な型が何かは問われない。従って、この例のように匿名クラスであっても構わない(ただ し、匿名クラスが利用できるのはアプリケーションが完全信頼で動作している場合のみ)。 ここで、DataContext プロパティの値は包含継承(親 UI 要素から値が引き継がれる)されていて、ビ ュー・クラスのDataContext プロパティに渡したデータ・ソースは、<TextBlock>要素の DataContext プロパティにも引き継がれている。「{Binding X}」というマークアップ拡張記述の実体としては、その UI 要素の DataContext プロパティに格納されているオブジェクトの X プロパティを参照することにな る。 バインディングのソースとターゲット データ・バインディングを行ううえで、データの提供元(=ソース)となるものと、反映先(=ターゲ ット)となるものができるが、混乱を避けるため、Figure 4 に示すように用語を定める。 ・バインディング・ソース: データの提供元のオブジェクト。 ・ソース・プロパティ: データの提供元となる、バインディング・ソースのプロパティ。 ・バインディング・ターゲット: データの反映先のオブジェクト。 ・ターゲット・プロパティ: データの反映先となる、バインディング・ターゲットのプロパティ。

(20)

Figure 4: データ・バインディングのソース/ターゲットに関する用語

まず、WPF のデータ・バインディングにおいて、ターゲット・プロパティは依存関係プロパティでな ければならない。幸い、WPF で XAML コード中に記述する要素のプロパティは、大部分が依存関係プ ロパティになっていて、ターゲット・プロパティにすることが可能である(ただし、InputBinding ク ラス(System.Window.Input 名前空間)の Command プロパティのように、.NET Framework 3.5 ま では依存関係プロパティになっておらず、.NET Framework 4 で初めてターゲット・プロパティにでき るようになったものもある)。 一方で、バインディング・ソースには以下のようなものが利用可能である。 ・CLR オブジェクト: 通常の CLR オブジェクトのプロパティやインデクサをソース・プロパティに できる(アクセス修飾子は「public」である必要がある。また、フィールドは利用できない)。プロパ ティ値の取得や更新はリフレクションを介して行われる。あるいは、ICustomTypeDescripter インタ ーフェイスを実装する場合や、TypeDescriptionProvider クラスを使って型情報を WPF に対して登 録している場合(いずれのクラスもSystem.ComponentModel 名前空間)には、これらを介して値の 取得・更新が行われる。また、値の変更を通知するためには、INotifyPropertyChanged インターフ ェイス(System.ComponentModel 名前空間)を実装する必要がある。 ・依存関係オブジェクト: ソース・プロパティに依存関係プロパティを用いる場合、パフォーマンス もよく、標準で値の変更を通知する仕組みも備えている。ただし、依存関係プロパティを利用するた めには、バインディング・ソースが依存関係オブジェクトである(DependencyObject を継承してい る)必要がある(その結果、ほかのクラスを継承できなくなる)という問題もある。

・XML オブジェクト: Binding マークアップ拡張の指定の仕方によっては(Source プロパティと XPath プロパティを利用)、XML オブジェクトを直接バインディング・ソースにすることができる。 ・動的オブジェクト: .NET Framework 4 からは、IDynamicMetaObjectProvider インターフェイス

を実装する動的オブジェクトもバインディング・ソースに指定可能になった。すなわち、IronPython やIronRuby などの動的言語を用いて作成したオブジェクトを、そのままバインディング・ソースに できる。 実際のところ、CLR オブジェクトをバインディング・ソースとして利用する場合が多いだろう。そこで 重要になるのが INotifyPropertyChanged インターフェイスを利用した値の変更通知である。この INotifyPropertyChanged インターフェイスの実装方法については、次回で説明する。 それでは、次のページからはBinding マークアップ拡張の書き方について詳しく説明していこう。 Binding マークアップ拡張の書き方 これまでの例では、Binding マークアップ拡張を単に「{Binding X}」というように記述してきたが、 Binding マークアップ拡張にはさまざまなプロパティがあり、データ・バインディングの挙動を細かく 設定することができる。以下、主要なものをいくつか紹介していく。 ○データ・バインディングの向きとタイミング Mode プロパティで、データ・バインディングの向きとタイミングを指定できる。Mode プロパティに 対して設定できる値は以下のとおりである。 ・OneTime: UI 要素生成時に一度だけ、ソース・プロパティの値を読み出してターゲット・プロパテ

(21)

ィに与える。 ・OneWay: ソース・プロパティが変更された際に、ターゲット・プロパティに変更を反映させる(逆 は行わない)。 ・OneWayToSource: ターゲット・プロパティが変更された際に、ソース・プロパティに変更を反映 させる。 ・TwoWay:ソース・プロパティおよびターゲット・プロパティのいずれの変更も、他方に反映させる。 当然、TwoWay や OneWayToSource を指定するためには、ソース・プロパティが書き込み可能である (set アクセサを持っている)必要がある。また、TwoWay や OneWayToSource を指定した際には、変 更をソース・プロパティに反映させるタイミングを UpdateSourceTrigger プロパティで指定できる。 UpdateSourceTrigger プロパティに設定できる値は以下のとおりである。 ・Default: バインディング・ターゲットの依存関係プロパティのメタデータに基づいてタイミングを 決定する(何も指定しない場合、これが設定される)。 ・PropertyChanged: バインディング・ターゲットの値が変化するたびに(例えば、テキストボック スの場合、1 文字入力されるたびに)変更を通知する。 ・LostFocus: バインディング・ターゲットの要素がフォーカスを失うたびに(例えば、テキストボッ クスからフォーカスを外した際に)変更を通知する。 ・Explicit: 明示的に UpdateSource メソッドを呼び出した場合にのみ変更を通知する。 ○バインディング・ソースのパスの書き方 Path プロパティで、バインディング・ソースのパスを指定する。Path プロパティは省略形で書くこと ができ、これまで用いてきた、 {Binding X} という書き方は、 と同じ意味である。Path プロパティでは、以下のような構文でパス指定が可能である。 ・「{Binding X.Y}」のように、「.」でつなぐことで階層的なパス指定が可能 ・「{Binding X[0]}」のように、角カッコを用いてインデクサを利用可能 ・データ・ソース(=DataContext に渡されたオブジェクト)そのもののインデクサを利用する場合に は、「{Binding [0]}」というように、Path プロパティに直接角カッコを記述する ・引数が複数あるインデクサを利用する場合には、「<Binding Path="[3,4]" />」のように「,」を利用す る(※ただし、マークアップ拡張ではこの書き方はできない。<Binding>要素を使う必要がある) ・階層的なパス指定とインデクサは混在可能(「{Binding X[0].Y}」というような記述も可能) ・添付プロパティをバインディング・ソースにする場合には、「{Binding (Canvas.Left)}」のように、 カッコでくくって「(型名.プロパティ名)」と記述する 例えば、List 3 に示すように、階層的なデータを DataContext プロパティに渡した場合を考えてみよう。 Visual Basic

(22)

{

.管理者 = New With {.姓 = "岩永", .名 = "信之"}, .コンテンツ =

{

New With {.タイトル = "C# 入門", .URL = "csharp"}, New With {.タイトル = "信号処理", .URL = "dsp"}, New With {.タイトル = "力学", .URL = "dynamics"} } } Visual C# this.DataContext = new { 管理者 = new { 姓 = "岩永", 名 = "信之" }, コンテンツ = new[] {

new { タイトル = "C# 入門", URL = "csharp" }, new { タイトル = "信号処理", URL = "dsp" }, new { タイトル = "力学", URL = "dynamics" }, }

};

List 3: 階層的なデータを DataContext に渡す

このとき、List 4 に示すような XAML コードを書くと、Figure 5 に示すような表示結果が得られる。 ……省略……

<StackPanel x:Name="panel" Canvas.Left="99"> <!-- 階層的なプロパティ指定 -->

<TextBlock Text="{Binding 管理者.姓}"/> <!-- インデクサー -->

<TextBlock Text="{Binding コンテンツ[1].URL}"/> <!-- 添付プロパティ -->

<TextBlock

Text="{Binding ElementName=panel, Path=(Canvas.Left)}"/> </StackPanel>

……省略……

List 4: パス指定の例(XAML)

(23)

○コレクション走査パスの指定

Path プロパティでは、「{Binding Path=List/X}」というように「/」でつなぐことで、リストボックス などで選択された行のプロパティを表示することができる(コレクションを走査して、選択中の要素を 拾い出してくれる)。

前節と同様に、List 3 に示すデータを DataContextプロパティに渡した場合、List 5 に示すようなXAML コードを書くとFigure 6 に示すような表示結果が得られる。

……省略…… <StackPanel>

<!-- 「/」による選択行の表示を働かせるためには

IsSynchronizedWithCurrentItem プロパティの指定が必要 --> <DataGrid ItemsSource="{Binding Path=コンテンツ}"

IsSynchronizedWithCurrentItem="True" />

<!-- DataGrid コントロールで選択されている行の [タイトル]プロパティが表示される -->

<TextBlock Text="{Binding Path=コンテンツ/タイトル}" /> </StackPanel>

……省略……

List 5: 階層的なデータを参照する Binding マークアップ拡張の書き方(XAML)

Figure 6: List 5 の表示結果 ※ DataGrid コントロール内の行を選択すると、その行データの[タイトル]列の値が、下部のテキス トボックスに表示される。 ○バインディング・ソースの明示的指定 特に何も指定しない場合、バインディング・ソースは、UI 要素の DataContext プロパティに与えられ たオブジェクトになる。一方で、Binding マークアップ拡張の Source プロパティを設定することで、 バインディング・ソースを明示的に指定することもできる。 例えば、List 6 に示すように、静的プロパティをバインディング・ソースに指定するなどの用途で利用 する。

(24)

……省略…… <StackPanel>

<TextBlock Text="{Binding Source={x:Static Application.Current}, Path=StartupUri}" />

</StackPanel> ……省略……

List 6: 静的プロパティをバインディング・ソースに指定する例(XAML)

※ このコードを実行すると、(Application クラス(System.Windows 名前空間)の Current 静的プロ パティから得られる)Application オブジェクトの StartupUri プロパティ値(=アプリケーション の起動時に自動的に表示されるUI を参照する URI)が、TextBlock コントロールに表示される。 Source プロパティのほかに、後述する ElementName プロパティや RelativeSource プロパティでもバ インディング・ソースを選択できるが、これら3 つのプロパティを同時に指定することはできない。 ○XPath

バインディング・ソースがXML データの場合、Path プロパティではなく XPath プロパティを利用す る。

XPath プロパティには、(XML に付随するパス指定言語である)XPath 言語(XML Path Language) でクエリを記述する。XPath クエリの書き方については本稿の範囲を超えるため、詳細は割愛し、例の みをList 7 に示す。

……省略…… <Grid>

<Grid.Resources>

<XmlDataProvider x:Key="Pages" XPath="Pages"> <x:XData>

<Pages xmlns="">

<Page Title="C# 入門" Url ="csharp" /> <Page Title="信号処理" Url ="dsp" /> <Page Title="力学" Url ="dynamics" /> </Pages>

</x:XData>

</XmlDataProvider> </Grid.Resources> <ListBox ItemsSource=

"{Binding Source={StaticResource Pages}, XPath=Page}"> <ListBox.ItemTemplate>

<DataTemplate>

(25)

</DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> ……省略…… List 7: XML データをバインディング・ソースとして利用する例(XAML) ※ このコードを実行すると、XML データの<Pages>要素内の各<Page>要素の Title 属性値が、リスト ボックスの各行に表示される。 ○ほかのUI 要素の参照 場合によっては、モデルなどのデータ・ソースを介さず、UI 要素間で直接プロパティ値の同期を取り たいことがある。このような場合に、ElementName プロパティを指定することで、ほかの UI 要素を バインディング・ソースにしてデータ・バインディングを行える。 例えば、List 8 に示すような XAML コードを書くことで、テキストボックスに表示されるテキストと、 スライダーの値を同期できる(スライダーのつまみを動かすと、即座にテキストが変化する)。 ……省略…… <StackPanel> <TextBox x:Name="textValue" />

<Slider Value="{Binding ElementName=textValue, Path=Text}" /> </StackPanel> ……省略…… List 8: UI 要素間でのデータ・バインディング(XAML) ○自分自身や先祖要素の参照 RelativeSource プロパティを指定することで、UI 要素自身や、先祖要素(=直接の親だけではなく、 階層的にたどれる上位の要素すべて)のプロパティをデータ・ソースに指定することができる。利用例 をList 9 に示す。 ……省略…… <StackPanel> <!-- 先祖をたどって<Window>要素を見つけ、 その Title プロパティとバインディング -->

<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},

Path=Title}" />

<!-- 自分自身の Width プロパティと Height プロパティを バインディング(要するに、正方形にする) --> <Rectangle Fill="Blue" Width="50"

Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}" /> </StackPanel>

(26)

……省略……

List 9: RelativeSource の利用例(XAML)

※ このコードを実行すると、TextBlock コントロールに先祖<Window>要素の Title プロパティ値が表 示され、その下に縦・横50 ピクセルの青い正方形が表示される。

○値の変換

値そのままではなく、何らかの変換処理を行ってからデータ・バインディングしたい場合もあるだろう。 そのような場合には、Binding マークアップ拡張の Converter プロパティに IValueConverter インター フェイス(System.Windows.Data 名前空間)を実装するクラスを渡す。

例えば、角度の度数(degree)と弧度数(radian:ラジアン)の(双方向)変換を考えてみよう。まず、 List 10 に示すように、IValueConverter インターフェイス実装クラスを用意する。

Visual Basic

Public Class DegreeToRadianConverter Implements IValueConverter

Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert

Dim x As Double = CType(value, Double) Return x / 180 * Math.PI

End Function

Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

Dim x As Double = Double.Parse(CType(value, String)) Return (x / Math.PI * 180).ToString()

End Function End Class Visual C# using System; using System.Windows.Data; using System.Globalization; namespace atmarkit05 {

public class DegreeToRadianConverter : IValueConverter {

(27)

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { double x = (double)value; return x / 180 * Math.PI; }

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

{

double x = double.Parse((string)value); return (x / Math.PI * 180).ToString(); } } } List 10: 度と弧度を変換するためのクラス(上:C#、下:VB) ※ なお VB の場合、プロジェクト・プロパティで設定した、[ルート名前空間]が「atmarkit05」なっ ている必要がある。 このクラスを利用して、List 11 に示すような XAML コードを記述することで、度と弧度の変換が可能 になる。 ……省略…… <StackPanel xmlns:local="clr-namespace:atmarkit05"> <StackPanel.Resources> <local:DegreeToRadianConverter x:Key="DegToRad"/> </StackPanel.Resources>

<Slider x:Name="slider" Value="0" Minimum="0" Maximum="360" /> <TextBox Text="{Binding ElementName=slider, Path=Value}" /> <TextBox Text="{Binding ElementName=slider, Path=Value, Converter={StaticResource DegToRad}}" />

</StackPanel> ……省略……

List 11: Converter プロパティを使った値の変換の例(XAML)

※ このコードを実行してスライダーを動かすと、上の TextBox コントロールに 0~360 の度数値が表 示され、その下のTextBox コントロールに(度数から変換された)弧度数が表示される。 なおVB の場合、プロジェクト・プロパティで設定した、[ルート名前空間]が「atmarkit05」なって いる必要がある。 ○データ検証 Binding マークアップ拡張では、エンド・ユーザーから入力されたデータの検証を行うために、以下の 3 つのプロパティを利用する。

(28)

・ValidationRules: ValidationRule クラス(System.Windows.Controls 名前空間)を継承するクラス を使って、明示的に検証ルールを追加する。 ・ValidatesOnExceptions: 「True」に設定されている場合、ソース・プロパティの更新中に例外が 発生していないかを確認する。ValidationRules プロパティに「ExceptionValidationRule」を追加し た場合と同様の挙動になる。 ・ValidatesOnDataErrors: 「True」に設定されている場合、IDataErrorInfo インターフェイスを介 したデータ検証を有効にする。ValidationRules プロパティに「DataErrorValidationRule」を追加し た場合と同様の挙動になる。 データ検証の結果、何らかのエラーがあった場合には、Validation.HasError 添付プロパティに「True」 が設定され、Validation.Errors 添付プロパティにエラーの一覧が格納される。 また、WPF のコントロールのいくつかは、標準でデータ検証エラーに対応していて、Figure 7 に示す ように、データ検証エラーがある場合にテキストボックスの枠線が赤くなるなどの変化が生じる (Validation.HasError 添付プロパティをトリガーとしたスタイルが定義されている)。 Figure 7: データ検証エラー時のスタイル ※ 数値をデータ・バインディングしているテキストボックスに対して、不正な文字列を入力すること で例外が発生している。ValidatesOnException プロパティを True に設定することで、このように、 テキストボックスの枠線が赤く変化する(ただし、この例で表示されているツールチップは、標準 ではなく、追加でスタイルを定義している)。 IDataErrorInfo インターフェイスの実装方法については、次回で説明する。 続いて、データ・バインディングした単一のデータ/コレクション・データの表示方法をカスタマイズ できる「データ・テンプレート」について説明する。 データ・テンプレート 文字列や数値などの単純な型だけでなく、任意のデータ型を表示するための仕組みとして、データ・テ ンプレートというものがある。前回で説明したコントロール・テンプレートと似ているが、名前どおり、 コントロール・テンプレートがコントロールの表示方法をカスタマイズするものであるのに対して、デ ータ・テンプレートはデータの表示方法をカスタマイズするものである。 ○ContentControl クラス:単一のデータに対するデータ・テンプレートの適用 まず、単一の(コレクションではない)データに対するテンプレートについて説明しよう。単一データ に対するテンプレート指定は、ContentControl クラス(Sytem.Windows.Control 名前空間)の Content プロパティにデータを、ContentTemplate プロパティにデータ・テンプレートを渡すことによって行う *。

(29)

のデータ・テンプレートの仕組みを利用可能である。

こ こ で は 、 標 準 の ク ラ ス ・ ラ イ ブ ラ リ で 提 供 さ れ て い る デ ー タ 型 の 中 か ら 、 式 ツ リ ー (System.Linq.Expressions 名前空間以下のクラス)を例にとって説明していく。まず、List 12 に示 すように、「(x, y) => x + y」という(ラムダ式の)式ツリーを DataContext プロパティに渡す。

Visual Basic

Dim sample As System.Linq.Expressions.Expression( _ Of Func(Of Integer, Integer, Integer)) = _

Function(x, y) x + y Me.DataContext = sample

Visual C#

System.Linq.Expressions.Expression<Func<int, int, int>> sample = (x, y) => x + y; this.DataContext = sample; List 12: 式ツリーをデータ・ソースにする例 比較のために、テンプレート指定のない場合にどうなるかを見てみよう。List 13 に示すように、Content プロパティのみを指定する。ソース・プロパティとして指定しているBody プロパティには、ラムダ式 の本体部分(=「x + y」の部分)が格納されている。 ……省略…… <StackPanel>

<ContentControl Content="{Binding Body}" /> </StackPanel> ……省略…… List 13: データ・テンプレートを指定しない場合(XAML) ※ データは、<ContentControl>要素の Content プロパティに指定する。 この場合、ただ単に「(x + y)」という文字列だけが表示されるはずだ。これは、式ツリーを ToString メソッドで文字列化したものが表示されている。 それでは、データ・テンプレートを指定してみよう。

List 14 に示すように、ContentTemplate プロパティに対して、<DataTemplate>要素を指定する。Figure 8 に表示結果を示す。データ・テンプレートを指定しなかった場合もまとめて表示している。図中の上 段が指定なし、下段が指定ありの場合の表示結果である。

……省略…… <Grid>

<ContentControl Content="{Binding Body}" Grid.Row="1" Grid.Column="1"> <ContentControl.ContentTemplate>

(30)

<StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding NodeType}" Background="LightBlue" /> <TextBlock Text=": " />

<TextBlock Text="{Binding Left}" Background="LightBlue" /> <TextBlock Text=", " />

<TextBlock Text="{Binding Right}" Background="LightBlue" /> </StackPanel> </DataTemplate> </ContentControl.ContentTemplate> </ContentControl> </Grid> ……省略…… List 14: データ・テンプレートを指定する場合(XAML) ※ データ・テンプレートは、<ContentControl>要素の ContentTemplate プロパティに指定する。

Figure 8: List 13 と List 14 の表示結果○データ・テンプレートの自動適用

データ・テンプレートは、もちろんリソース化して複数の<ContentControl>要素間で共有できる。ま た、コントロール・テンプレートで自動適用ができたように(具体的には、<ControlTemplate>要素に TargetType プロパティを設定)、データ・テンプレートも<DataTemplate>要素の DataType プロパテ ィを設定することで、指定した型に対してデータ・テンプレートを自動適用できる。

例えば、List 14 の例は、List 15 のように書き換えられる。 ……省略……

<Grid xmlns:exp="clr-namespace:System.Linq.Expressions;assembly=System.Core"> <Grid.Resources>

<DataTemplate DataType="{x:Type exp:BinaryExpression}"> <StackPanel Orientation="Horizontal">

(31)

<TextBlock Text="{Binding NodeType}" Background="LightBlue" /> <TextBlock Text=": " />

<TextBlock Text="{Binding Left}" Background="LightBlue" /> <TextBlock Text=", " />

<TextBlock Text="{Binding Right}" Background="LightBlue" /> </StackPanel>

</DataTemplate> </Grid.Resources>

<ContentControl Content="{Binding Body}" /> </Grid> ……省略…… List 15: データ・テンプレートの自動適用の例(XAML) ※ データ・テンプレートをリソース化して<DataTemplate>要素の DataType プロパティを設定するこ とで、指定した型に対して一括でデータ・テンプレートを自動適用できる。この例では、System.Core アセンブリに含まれるBinaryExpression 型(System.Linq.Expressions 名前空間)のデータすべて に対して、<DataTemplate>要素で定義されたデータ・テンプレートが自動的に適用される。 データ・テンプレートの自動適用は、型の混在する階層的なデータに対してテンプレートを適用したい ときに特に重宝することだろう。 例えば、式ツリーは、その名前どおり、階層的なデータ構造をしているので、List 15 に例示した BinaryExpression クラスなら、Left プロパティや Right プロパティも式ツリーになっていて、 BinaryExpression クラスをはじめとするさまざまなクラスが格納されている。このとき、Left プロパ ティなどを再度<ContentControl 要素>の Content プロパティにバインディングしておけば、階層的な テンプレート適用が行われる。 式ツリーを構成するさまざまな型すべてに対してデータ・テンプレートを適用できるリソース・ファイ ルを、下記のURL からダウンロードできるようにしたので、興味がある場合にはダウンロードしてみ ていただきたい。リソース・ファイルの利用方法は、本連載第4 回の「●外部リソースの取り込み」の 項を参照してほしい。 ・式ツリー表示用のリソース・ファイル(ExpressionTreeTemplates.xaml)のダウンロード(右クリ ックして[対象をファイルに保存]) このデータ・テンプレート一式を使って、

(32)

(int x, int y) => (x + 3) * (y - 1) というラムダ式から得られた式ツリー全体を表示すると、Figure 9 のようになる。 Figure 9: 階層的なデータに対してデータ・テンプレートを適用した例 ※ ここまでの実行例では「{Binding Body}」としてラムダ式の本体部分の式ツリー(=Body プロパテ ィ)のみを表示していたが、この例では「{Binding}」(=「{Binding Path=.}」とも記述可能)とし てラムダ式全体(=バインディング・ソース)の式ツリーを表示している。 ○ItemsControl クラス:コレクションに対するデータ・テンプレートの適用 データ・ソースがコレクションの場合、ItemsControl クラス(Sytem.Windows.Control 名前空間)の ItemsSource プロパティにデータを、ItemTemplate プロパティにデータ・テンプレートを渡すことに よってデータ・バインディングを行う。ItemTemplate プロパティに指定したテンプレートは、コレク ションの要素1 つ 1 つに対して適用される。また、この場合にも、(単一のデータの場合と同様に)デ ータ・テンプレートの自動適用は有効である。

List 12 と同じデータ(「x + y」という式ツリー)に対して、List 16 に示すような XAML コードを書く と、Figure 10 のような表示結果が得られる。ソース・プロパティとして指定している Parameters プ ロパテ ィには、ラムダ式の引 数リストの部分(=「x, y」の部分)が格納されていて、これは ParameterExpression 型のリストになっている。各要素には、型に基づいてテンプレートが自動適用さ れる。 ……省略…… <Grid xmlns:exp="clr-namespace:System.Linq.Expressions;assembly=System.Core">

Figure 4 に示すように、&lt;Style&gt;要素に対する IntelliSense が有効になる。(Visual Studio 2010 から、
Figure  1 に示すように、この XAML エディタは、マウスを使って視覚的にデザインするための「デザ イン」画面(画像上部)と、XAML コードを文字ベースで入力する「XAML」画面(画像下部)を持っ ている。そして、どちらか片方への変更があった場合、もう一方に即座に変更結果が反映される。
Figure 1:  同じデータを複数のコントロール上に表示する例(XAML エディタ)  このような機能を実現するためには、データの変更を通知するような仕組みが必要で、このために WPF は「データ・バインディング」という仕組みを持っている(また、次回説明する「コマンド」も、ビュ ーとモデルの疎結合を保つために有用である)。次節以降では、このデータ・バインディングについて 説明していく。  ■  データ・バインディング  WPF では、データ・ソース(=モデルなどの、データの提供元)をビュー(=WPF の
Figure 5: List 4 の表示結果
+4

参照

関連したドキュメント

So far, most spectral and analytic properties mirror of M Z 0 those of periodic Schr¨odinger operators, but there are two important differences: (i) M 0 is not bounded from below

In addition, under the above assumptions, we show, as in the uniform norm, that a function in L 1 (K, ν) has a strongly unique best approximant if and only if the best

The explicit treatment of the metaplectic representa- tion requires various methods from analysis and geometry, in addition to the algebraic methods; and it is our aim in a series

We have avoided most of the references to the theory of semisimple Lie groups and representation theory, and instead given direct constructions of the key objects, such as for

Bipartite maps (also called hypermaps, or dessins d’enfants ) : vertices are either black or white, and monochromatic edges

28 “Every [cognition] which grasps something totally dissimilar as being similar in fact has a similarity based on exclusion of others as its object, just as a cloth, although

Thank you, Sabers Nation, for your participation in the coronavirus SA- BERStrong Pushup Challenge. Sabers students, teachers, graduates, and parents showed their mental and