スタイルは“見た目”に限定された機能ではなく、UI要素のプロパティ値(依存関係プロパティに限る)を一括管理するための機能である。
UI要素のプロパティというのは、その多くが“見た目”に関係したプロパティであるため、結果的にはスタイルによって“見た目”の統一、保守を簡単に行うことができる。しかしながら、次回で紹介するコントロール・テンプレートとの混同を防ぐためにも、あくまで「プロパティ値を一括管理するための汎用的な機能である」とご理解いただきたい。
なお、WPFでは、プロパティ値だけでなくイベント・ハンドラやXAMLコード上でアクションを実行する機能であるトリガについても、スタイルを使って共有することが可能であるが、本稿での紹介は割愛する。
●スタイルの機能
スタイルの機能は、FrameworkElementクラスが持つStyleプロパティにStyleオブジェクトを設定するという方法で使用する。Resourcesプロパティと同様、StyleプロパティはFrameworkElementクラスで実装されているため、ほぼすべてのUI要素でスタイルを使用できると考えてよい。
では、いつものように具体例を使ってスタイルの機能を確認していくことにしよう。
下記のコードは、Ellipseオブジェクトの3つのプロパティに対して、プロパティ属性構文を使って値を設定する何の変哲もないコードである。
<Ellipse Fill="Blue" Height="80" Width="160"/>
このコードを、スタイルを使って書き換えると下記のようになる。
<Ellipse>
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
</Style>
</Ellipse.Style>
</Ellipse>
Styleクラスのコンテンツ・プロパティ(=プロパティ要素構文でプロパティ名を省略できるプロパティ)はSettersプロパティ(SetterBaseCollection型)となっており、1つ以上のSetterオブジェクトを設定できる。Setterオブジェクトでは、設定したいプロパティ名をPropertyプロパティに、そのプロパティに設定したい値をValueプロパティに設定する。
また、StyleクラスはTargetTypeというプロパティ(Type型)を持っており、このプロパティにはスタイルの対象となる型を設定しておく必要がある。
Type型のプロパティでは、型名の文字列からTypeオブジェクトへの自動変換が行われるが、WPFでは下記のようにx:Typeマークアップ拡張機能を使用して、明示的にTypeオブジェクトを設定することもできる。
<Style TargetType="{x:Type Ellipse}">
通常、TargetTypeプロパティに値を設定しないと例外がスローされるが、WPFではSetterオブジェクトのPropertyプロパティの設定により型を修飾することで、TargetTypeプロパティを省略することが可能だ。
<Ellipse>
<Ellipse.Style>
<Style>
<Setter Property="Ellipse.Fill" Value="Blue"/>
<Setter Property="Ellipse.Height" Value="80"/>
<Setter Property="Ellipse.Width" Value="160"/>
</Style>
</Ellipse.Style>
</Ellipse>
●プロパティ値の優先順位
Settersプロパティの設定では、同じPropertyプロパティ値を持つ複数のSetterオブジェクトがあっても、特に例外は発生しない。その場合、最後に宣言された、つまり最下部に宣言されているSetterオブジェクトが有効となる。
例えば下記のように、Fillプロパティに対して、1番目のSetterオブジェクトで青色のブラシを、4番目のSetterオブジェクトで緑色のブラシを設定している場合、最終的に適用されるブラシは緑色となる。
<Ellipse>
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
<Setter Property="Fill" Value="Green"/>
</Style>
</Ellipse.Style>
</Ellipse>
さらに、スタイルを使った値の設定よりも、直接設定したプロパティ値が優先されるため、下記のコードでは最終的に赤色のブラシがFillプロパティの値として適用される。
<Ellipse Fill="Red">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
<Setter Property="Fill" Value="Green"/>
</Style>
</Ellipse.Style>
</Ellipse>
●スタイルの共有
これまでのコード例では、StyleオブジェクトをStyleプロパティに直接設定するという書き方をしてきたが、このような使い方では通常のプロパティ設定を冗長に記述しているに過ぎず、何の意味もなさないことはすでにお気付きだろう。スタイルは、リソースとしてリソース・ディクショナリに定義し、複数のUI要素で共有して初めて意味を持つ機能となる。
下記のように、StackPanelのResourcesプロパティに<Style>要素を宣言し、<Ellipse>要素ではStaticResourceマークアップ拡張機能を使ってリソース・キーを指定することで、スタイルにあるプロパティ設定を3つの<Ellipse>要素で共有することができる。
<StackPanel>
<StackPanel.Resources>
<Style x:Key="EllipseStyle" TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
</Style>
</StackPanel.Resources>
<Ellipse Style="{StaticResource EllipseStyle}"/>
<Ellipse Style="{StaticResource EllipseStyle}"/>
<Ellipse Style="{StaticResource EllipseStyle}"/>
</StackPanel>
また、派生元のクラスが共通で、そのクラスに存在するプロパティに限定されるのであれば、異なるクラス間でも同じスタイルを共有することができる。
下記のコードで使用しているEllipse、Rectangle、Polygonの3つのクラスは、すべてShapeクラスの派生クラスとなっており、スタイルで設定している3つのプロパティ、Fill、Height、WidthもShapeクラスに存在しているプロパティである。
<StackPanel>
<StackPanel.Resources>
<Style x:Key="ShapeStyle" TargetType="Shape">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
</Style>
</StackPanel.Resources>
<Ellipse Style="{StaticResource ShapeStyle}"/>
<Rectangle Style="{StaticResource ShapeStyle}"/>
<Polygon Style="{StaticResource ShapeStyle}" Points="0 80, 80 0, 160 80"/>
</StackPanel>
このような条件を満たしている場合、異なるクラスで1つのスタイルを共有することが可能だ。
●スタイルの継承
スタイルは、継承元となるスタイルをStyleクラスのBasedOnプロパティに設定することで継承することもできる。なお、Silverlight 2のStyleクラスにはBasedOnプロパティが存在しないが、Silverlight 3 Beta 1では追加されている。
下記がスタイルを継承するコードの例だ。
<StackPanel>
<StackPanel.Resources>
<Style x:Key="EllipseBaseStyle" TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
</Style>
<Style x:Key="EllipseStyle" TargetType="Ellipse"
BasedOn="{StaticResource EllipseBaseStyle}">
<Setter Property="Stroke " Value="Yellow"/>
<Setter Property="StrokeThickness" Value="10"/>
</Style>
</StackPanel.Resources>
<Ellipse Style="{StaticResource EllipseBaseStyle}"/>
<Ellipse Style="{StaticResource EllipseBaseStyle}"/>
<Ellipse Style="{StaticResource EllipseStyle}"/>
</StackPanel>
BasedOnプロパティにはStyleオブジェクトを設定する必要があるため、StaticResourceマークアップ拡張機能を使用して継承元のスタイル・オブジェクトを取得している。
なお再確認となるが、StaticResourceマークアップ拡張機能によるリソース参照はアプリケーション起動時のXAMLコードの処理順序に依存する。上記コードの2つのスタイルを逆の順序で宣言した場合には、例外が発生してしまうということに注意していただきたい。
●スタイルの暗黙的な適用
WPFでは、リソース・キーを使ってスタイルを参照する方法のほかに、特定の型に対してスタイルを暗黙的に適用させる方法も用意されている。
下記のコードのように、スタイルのx:Key属性をEllipse型(Typeオブジェクト)に設定した場合、スタイルを参照可能な範囲に存在するすべての<Ellipse>要素に対して、そのスタイルが暗黙的に適用される。
<StackPanel>
<StackPanel.Resources>
<Style x:Key="{x:Type Ellipse}" TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
</Style>
</StackPanel.Resources>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
なおx:Key属性を省略した場合、TargetTypeプロパティに指定したTypeオブジェクトが自動的にx:Key属性の値として設定されるため、上記のコードと同様の動作となる。
●Silverlight ToolkitのImplicitStyleManager
Silverlightの場合、標準では、型を指定して暗黙的にスタイルを適用させる機能が用意されていないが、Silverlight Toolkit(2009年6月現在の最新バージョンは「March 2009版」)のImplicitStyleManagerで同様の機能が提供されているため、簡単に紹介しておこう。
使い方はとても簡単で、下記のコードのように、スタイルがリソースとして定義されている要素(ここではStackPanel)に、ImplicitStyleManagerクラスのApplyMode添付プロパティを設定するだけである。
<StackPanel
xmlns:theming="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.Toolkit"
theming:ImplicitStyleManager.ApplyMode="Auto">
<StackPanel.Resources>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Width" Value="160"/>
</Style>
</StackPanel.Resources>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
ApplyMode添付プロパティの値は、スタイルの適用を行わない“None”、アプリケーション起動時に一度だけスタイルを適用する“OneTime”、動的に追加されたUI要素に対してもスタイルを適用する“Auto”の3つが用意されている。
このように、個々のプロパティ値を1つ1つリソース化するのではなく、スタイルを用いてUI要素単位でリソース化することで、プロパティ設定の統一が楽に行え、後々の保守作業も軽減できる。
引き続き次回は、WPF UIフレームワークの“見た目”に関する機能として、コントロール・テンプレートを紹介する。
Copyright© Digital Advantage Corp. All Rights Reserved.