Silverlight2に、VisualStateManagerというものがある。
(WPF Toolkitにも入ってるのでWPF Toolkitを入れればWPFでも使えそう(未確認))
このVisual State Managerという代物は一体何をしてくれるのか?というのがずっと疑問だった。
直訳すると、見た目の状態管理する人となる。具体的に言うと、どういう状態のときに、どういうアニメーションを起こすかということw管理してくれるものらしい。
状態には、名前をつけて管理することが出来て、コードからは名前を指定するだけで一連のアニメーションを動かすことが出来るようになる。ということで、非常に簡単な例だけど、ボタンをクリックすると文字が黒から赤色になって、もう一度クリックすると黒に戻るというものをVisualStateManagerを使って作ってみようと思う。
土台の作成
まずは、Visual State Managerとはまったく関係無い部分を作りこんでいく。
といっても今回はボタンが1つ置いてある画面を作るだけなので大して書くことは無い。
VSMSampleという名前でSilverlightアプリケーションを作成して。Page.xamlを以下のように編集した。
<UserControl x:Class="VSMSample.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<Button x:Name="button1" Content="ここの色が変わります" Foreground="Black" />
</Grid>
</UserControl>
実行結果は下のような感じ。ボタンがあるだけです。
VisualStateGroupの作成
ついにVisual State Manager関係のコードの作成に入ります。
VisualStateManagerは、VisualStateGroupsという添付プロパティを持っています。ここにVisualStateGroupを複数定義することが出来るようになっています。
今回は、ボタンの文字色が黒の時と、赤の時の2パターンあるのでVisualStateGroupを2つ定義します。VisualStateGroupの中にVisualStateを複数定義することが出来ます。
VisualStateには、x:Nameを使って名前をつけることが出来ます。今回はボタンの文字が赤色になる状態を表すredStateと、通常の状態を表すnormalStateの2つを定義しました。
<UserControl x:Class="VSMSample.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<!-- VisualStateManager.VisualStateGroups添付プロパティ -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="redState">
<!-- ボタンの文字が赤色の状態 -->
</VisualState>
<VisualState x:Name="normalState">
<!-- ボタンの文字が黒色の状態 -->
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Button x:Name="button1" Content="ここの色が変わります" Foreground="Black" />
</Grid>
</UserControl>
ここにアニメーションを定義していきます。
VIsualStateのStoryboardプロパティにStoryboardを定義することでアニメーションを定義します。ここでは、redStateに、ボタンの前景色を赤色にするアニメーションをs鋳込みます。
色のアニメーションは、ColorAnimationを使って実現します。
ColorAnimationにStoryboard.TargetNameプロパティとStoryboard.TargetPropertyプロパティを設定してボタンの前景色を指定します。
Storyboard.TargetPropertyは、若干複雑になっていますが、これはButtonのForegroundプロパティにはSolicColorBrushが指定されていて、それのColorプロパティをアニメーションの対象に指定しているためです。
<!-- VisualStateManager.VisualStateGroups添付プロパティ -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="redState">
<VisualState.Storyboard>
<!-- button1のForegroundを赤色へアニメーションする -->
<Storyboard>
<ColorAnimation Storyboard.TargetName="button1"
Storyboard.TargetProperty="(Button.Foreground).(SolidColorBlush.Color)"
To="Red" />
</Storyboard>
</VisualState.Storyboard>
</VisualState>
<VisualState x:Name="normalState">
<!-- 特に何もアニメーションしない(デフォが黒色なので)-->
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
状態の遷移
これでVisualStateGroupの定義が終わったので、後は状態を遷移させる処理を書くだけです。
状態を遷移させるためには、VisualStateManager.GoStateを使います。
GoStateには、第一引数に状態を遷移させるコントロールのインスタンス、第二引数に状態名、第三引数にVisualTransitionを使うかどうかを指定します。
VisualTransitionは、今回は使ってない(というか何かよくわかってない)ので第三引数にはfalseを指定します。
第二引数には、遷移したいステータス名なのでクリックの度にredState, normalStateを交互に渡します。
第一引数には、botton1ではなく、このページ自体を現すthisを渡します。
button1を渡してもアニメーションはしません。何故ならVIsualStateManager.VisualStateGroupsを指定しているのはLayoutRootという名前のGridだからです。
VisualState内で、button1という名前で要素にアクセスしたかったのでButtonよりも外側の要素にVisualStateGroupsを設定したためです。
上記の理屈ならLayoutRootをそのまま渡せばいいんじゃない?ってなるのですが、GridはControlを継承していないため、Gridの親のPage自信を渡しています。
ボタンクリックのタイミングで状態を遷移させるので、Clickイベントを定義します。
<Button x:Name="button1" Content="ここの色が変わります" Foreground="Black" Click="button1_Click" />
Clickイベントで、GoStateメソッドを使って状態を繊維させます。
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace VSMSample
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
}
// redState -> normalState -> redState -> normalState ...と文字列を返す
// IEnumerator<string>を作る。
private IEnumerator<string> stateGenerator = CreateStateGenerator();
private static IEnumerator<string> CreateStateGenerator()
{
while (true)
{
yield return "redState";
yield return "normalState";
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
// 次の状態へ遷移する
stateGenerator.MoveNext();
VisualStateManager.GoToState(
this, stateGenerator.Current, false);
}
}
}
これで完成です。
実行させて、ボタンをクリックすると文字の色が赤・黒・赤・黒とクリックのたびに変わります。
初期状態
一度クリックすると赤色へ(1秒かけてアニメーションします)
もう一度クリックすると黒色に戻ります
ちゃんと動いてる。
これはお手軽でいいかもしれない。Triggerよりも見た目を管理することに専念するように作られているのが好印象でした。