WPFはコントロールの外観を自由自在にカスタマイズできる柔軟性を備えている。これを実現する仕組みであるリソース、スタイル、テンプレートを解説する。
powered by Insider.NET
前回説明した依存関係プロパティは、「ほかの要素の値に依存してプロパティの値を決定する機構」といえる。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>
このように、リソースを用いてオブジェクトを共有することで、インスタンス生成などのオーバーヘッドを削減できる。上述の例では、青色のブラシを1つだけ生成して共有しており、2つの<Button>要素でそれぞれ個別にブラシを作るよりも生成されるインスタンスが少ない。
また、リソースは、UI要素に対する設定を一カ所に集めることで、保守を容易にする。上述の例でいうと、リソース中で定義されたブラシを変更するだけで、これを参照する2つの<Button>要素の背景色を一斉に変更できる。
●2種類の「リソース」
「リソース」という言葉が混乱を招く場合もあるので、ここで補足的な説明を入れておく。
Windowsアプリケーションでは、実行可能ファイル(=.EXEファイル)などのアセンブリの中に画像などのバイナリ・ファイルを埋め込むための「リソース」機構を持っている。この仕組みは、WPFアプリケーションでももちろん利用できる。この機構と、本稿で説明するWPFのリソース機構を区別する際には、以下のように呼び変える。
●リソース定義
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プロパティについては、後述のスタイルの節で詳しく説明する)。
<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>
<!-- 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>
●外部リソースの取り込み
リソースは<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 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">
<Window.Resources>
<ResourceDictionary Source="Styles.xaml"/>
</Window.Resources>
<Grid>
<Button Content="ボタン"/>
</Grid>
</Window>
また、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>
ちなみに、通常、リソースはインスタンスが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}" />
●リソース利用
リソースを参照するためには、StaticResourceマークアップ拡張もしくはDynamicResourceマークアップ拡張を利用する。実行時に値を変化させる必要のないものにはStaticeResourceマークアップ拡張を用いる。一方、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>
●システム・リソース
XAMLコードの中で定義したリソースに加えて、「個人設定」などで設定されたシステム色やフォントなどの、システム・リソースも利用できる。システム・リソースを利用するには以下のクラス(いずれもSystem.Windows名前空間)を使用する。
システム・リソースは、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>
この例ではDynamicResourceマークアップ拡張を利用しているが、こうすることで、アプリケーション起動中にエンド・ユーザーがデスクトップの背景色を変更した際に、その変更がアプリケーションに即座に反映される。
例えば、List 8の表示結果はFigure 3のようになる。この例では、デスクトップの背景色を灰色からピンクに変更している。
Copyright© Digital Advantage Corp. All Rights Reserved.