●VisualStateManager(VSM)
VSMは、動的な外観状態と、その外観状態に遷移する条件や遷移にかかる時間を管理する一連の機能を提供してくれるもので、以下の3つのクラスから成り立っている。
このうち、最低限必要になるのはVisualStateGroupクラスとVisualStateクラスの2つオブジェクトだ。まずこの2つについて説明する。
すべてのコントロールは、決められた条件において、決められた名前のVisualState(状態)に自動的に遷移するように実装されている。例えば、Silverlight 2のButtonコントロールの場合、その条件とVisualStateの名称は、下記の表のようになっている。
VisualStateGroup名 | VisualState名 | 状態(条件) | |
---|---|---|---|
FocusStates | Unfocused | コントロールがフォーカスを失ったときの状態 | |
Focused | コントロールがフォーカスを持ったときの状態 | ||
CommonStates | MouseOver | コントロール上にマウス・カーソルが重なったときの状態 | |
Pressed | コントロールがクリックされたときの状態 | ||
Disabled | コントロールが無効にされているときの状態 | ||
Normal | デフォルトの状態 | ||
Silverlight 2のButtonコントロールのVisualStateGroup名とVisualState名の一覧 |
つまり、MouseOverという名称のVisualStateを作成し、そこで色をオレンジ色に変化させるという外観定義を行うだけで、Buttonコントロールの上にマウス・カーソルが重なったときに、自動的にButtonコントロールの色がオレンジ色に変化するという動作を実現することができる。なお、外観定義はVisualStateコンテンツ・プロパティ(Storyboardプロパティ)に対して、Storyboard(アニメーション)を設定するという方法を用いる。
ちなみに、Silverlight 2の各コントロールの既定のVisualStateGroupとVisualStateの名称は、MSDNライブラリのコントロールのスタイルとテンプレートで確認できる。
では、具体的なXAMLコードで、その宣言方法を見ていこう。下記のコードは、先ほどまでのコントロール・テンプレートにVSMの設定を追加し、コントロール上にマウス・カーソルが重なったときに色がオレンジ色に変化するように記述したものである。
<ControlTemplate x:Key="ButtonTemplate" TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="30" Padding="10" x:Name="border">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="Orange" Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
VSMは、VisualStateMangaerクラスのVisualStateGroups添付プロパティに対してVisualStateGroupを追加する形で定義していく。VisualStateGroups添付プロパティの添付対象はFrameworkElementとなっており、コントロール・テンプレート内の添付可能な場所であれば、どこに宣言しても構わない。
上記のコードでは、「CommonStates」というVisualStateGroupに含まれる「Normal」と「MouseOver」の2つのVisualStateが宣言されており、後者には色を変化させるためのアニメーションが設定されている。NormalのVisualStateは省略してもよさそうに思えるが、MouseOverの状態に遷移した後に通常の状態に戻る際、戻り先の状態であるNormalがないと戻る状態がなくなってしまうため、不可欠なVisualStateである。
先ほどまでのXAMLコードのコントロール・テンプレート部分を上記のコードに変更して実行すると、以下の画面のように、マウス・カーソルが重なったときにボタンの色が変化する動作を確認できる。
VSMは、動的な外観を状態ごとに分けて定義できるため、UIをデザインする立場の人にとって分かりやすいだけでなく、Expression Blendといったツールによるビジュアル編集のサポートが行いやすいというメリットも持っている。
●VisualTransition
上記の例では、Buttonコントロールにマウスを重ねた瞬間にButtonコントロールの色が変化するようになっていた。つまり、“Normal”から“MouseOver”へ状態が瞬時に遷移しているということだ。
これを瞬時に遷移するのではなく、例えば2秒間かけて遷移するようにしたい場合、“MouseOver”のVisualStateに定義されたアニメーションを2秒間かけて再生する(上記のコード例の場合、ColorAnimation要素のDuration属性を“0:0:2”と記述する)ことで実現できる。
では、その逆の遷移、“MouseOver”から“Normal”への状態遷移を1秒間かけて行うようにしたい場合にはどうすればよいのだろうか。この場合、“Normal”は通常の状態、つまり何も変化しないという状態であるため、外観の定義としてアニメーションは定義していない。そのため、アニメーションの再生時間を変更する方法で対応することはできない。
また、扱う状態が3つ以上になってくると、特定の状態からの遷移だけを1秒間かけて行うといった動作が必要になることも想定できる。そのため、VSMでは、個々のアニメーションの再生時間により状態の遷移時間を調整する代わりに、「「VisualTransition(外観遷移)」を使う方法が用意されている
VisualTransitionは、暗黙的なコレクション構文によりVisualStateGroupクラスのTransitionsプロパティに対して追加する形で使用する。状態の遷移時間は、VsiualTransitionクラスのGeneratedDurationプロパティに時間を設定することで指定できる。この設定値は、対象となる個々のアニメーションに設定された再生時間よりも優先される。
そして、VisualTransitionクラスのToプロパティで遷移元のVisualSateを、Fromプロパティで遷移後のVisualSateを指定することで、VisualTransitonの適用範囲を制限できる。FromプロパティとToプロパティの設定値と、それに対する制限の種類は下の表のとおりだ。
制限の種類 | Fromプロパティの値 | Toプロパティの値 |
---|---|---|
指定したVisualStateから別の指定したVisualStateまで | VisualStateの名前 | VisualStateの名前 |
任意のVisualStateから指定したVisualStateまで | 設定しない | VisualStateの名前 |
指定したVisualStateから任意のVisualStateまで | VisualStateの名前 | 設定しない |
任意のVisualStateから別の任意のVisualStateまで | 設定しない | 設定しない |
FromプロパティとToプロパティの設定値に対応する制限の種類 |
では、VisualTransitionを使った具体的なコードを見ていくことにしよう。
以下は、ボタンがクリックされたときに適用されるVisualStateとして“Pressed”を追加し、“Pressed”の状態からのほかのすべての状態への遷移時間を2秒間に設定したコード例である。
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="Orange" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Storyboard.TargetName="border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="Green" Duration="0"/>
</Storyboard>
</VisualState>
<VisualStateGroup.Transitions>
<VisualTransition From="Pressed" GeneratedDuration="0:0:2"/>
</VisualStateGroup.Transitions>
</VisualStateGroup>
上記のコード例におけるVisualTransitionは、Fromプロパティに“Pressed”を設定し、Toプロパティを設定していないため、“Pressed”状態からのすべての遷移に対して適用される。マウス・カーソルをボタンの上に重ねたときは“Normal”から“MouseOver”へ状態が遷移するためVisualTransitionは適用されないが、ボタンをクリックしたときには“Pressed”から“MouseOver”へ状態が遷移するため、2秒間かけて色がオレンジ色に変化することになる。
なお、VisualTransitionクラスには、ほかにもコンテンツ・プロパティであるStoryboardプロパティが用意されており、特定の遷移に対してアニメーションを追加することもできる。
ちなみに、コントロール・テンプレートの作成からVSMの設定までをExpression Blend 2.5 プレビュー(後にExpression Blend 2 SP1として正式リリースされたもの)で行う一連の流れを、「Expression Blendで体験するSilverlightコントロールの「デザイン力」」で解説しているので、Expression Blendを使用する機会のある方は、ぜひこちらもご一読いただきたい。
●最後に
最後に、これまでの内容を整理するという意味も含め、以下のWPF UIフレームワークのUI要素におけるクラス階層図を紹介して、本連載を締めたいと思う。
UIElementクラスは、WPF UIフレームワークにおけるUIの基本クラスで、UIの要素として配置できるのは、UIElementクラスとその派生クラスである。この点が、フォームに貼り付けることができるクラスはすべてControlクラス(System.Windows.Forms名前空間)の派生クラス、つまりコントロールであったWindowsフォームとの大きな違いになる。
そして、UIElementクラスの唯一の直接的な派生クラスとなるFrameworkElementクラスでは、リソースの配置やスタイルといった機能が追加されている。図を見ていただいてお分かりかと思うが、UIElementクラスを除くWPF UIフレームワークのすべてのUI要素で、リソースの配置やスタイルを利用できるようになっている。
さらに、ButtonやListBoxといったControlクラスの派生クラスになると、コントロール・テンプレート機能が利用可能だ。FrameworkElementクラスの直接的な派生クラスとなるBorderやRectangleなどのプリミティブな要素は、表示専用か、もしくはコントロール・テンプレート内で外観を構成する要素として使われることが多い。それに対して、ユーザーとのインタラクションが必ず発生し、確固たる役割を持ったUI要素がコントロールということになる。
本連載には登場しなかったUI要素や、将来追加される新たなUI要素も、この階層図に当てはめて考えることで楽に理解できるのではないだろうか。
筆者自身は、これまで行き当たりばったりでWPF UIフレームワークについて学んできたが、本連載で紹介した内容は「これさえ覚えてしまえばもう大丈夫」というものを厳選し、理解しやすいように順序立ててまとめたものである。
本連載の内容をすべて理解すれば、WPF UIフレームワークの基礎はもう十分なはずだ。あとは実践あるのみなので、ぜひWPFやSilverlightのアプリケーション開発にチャレンジしてほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.