VisualStateManager帶來了什么?
在WPF中我們可以用各種Trigger配合Animation,Template來實現絢麗控件的外觀變換,用后台邏輯來定義和標注控件不同的狀態。.Net Framework 4.o開始引入了VisualStateManager,主要為了控制控件的狀態轉換,和其間涉及的外觀行為。從控件狀態遷移層面上管理空間的外觀行為,在設計級別上感覺層次更清晰,邊界更明確。VisualStateManager用途:
1. 管理控件狀態。
2. 管理控件狀態的轉換邏輯。
我們通過VisualStateManager控件的狀態、控件在特定狀態下以及控件更改狀態時的外觀。更清晰的表述我們看下MSDN原文:
The VisualStateManager enables you to specify:
1. states for a control
2. the appearance of a control when it is in a certain state.
3. the appearance of a control when a control changes states.
VisualStateManager相關類
VisualStateManager常用類的相關結構,是比較典型的層層深入的包含結構,我們這里說明一些每一個層次的作用:
1. 通過在控件上設置VisualStateManager.VisualStateGroups附加屬性向控件添加可視狀態。
2. VisualStateGroups包含VisualStateGroup對象集合。
3. VisualStateGroup包含互斥的VisualState對象的集合。 在每個VisualStateGroup中,控件都始終恰好處於一個狀態。
4. VisualState用於定義控件狀態,和處於某個狀態時的外觀。
5. VisualState 包含Storyboard對象的集合,用於指定控件處於該狀態時如何更改控件的外觀。
下面的XAML更直觀形象:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="XXX">
<VisualState x:Name="State001">
<Storyboard>
<Animation_A1 .../>
<Animation_B1 .../>
...
</Storyboard>
</VisualState>
<VisualState x:Name="State001">
<Storyboard>
<Animation_A2 .../>
<Animation_B2 .../>
...
</Storyboard>
</VisualState>
...
</VisualStateGroup>
...
</VisualStateManager.VisualStateGroups>
我們這邊給出一個實際定義VisualStateManager.VisualStateGroups的例子,在Windows Resource中定義一個Button Template,隨着鼠標的Enter和Leave標識兩種狀態,根據不同的狀態來顯示不同的外觀:rectangle或ellipse。
<Window.Resources>
<ControlTemplate x:Key="ButtonColorChangeStyleTemplate" TargetType="{x:Type Button}">
<Grid x:Name="LayoutRoot" Background="Transparent" MouseEnter="ShapeChangeMouseEvent" MouseLeave="ShapeChangeMouseEvent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Layout">
<VisualState x:Name="EllipseState">
<Storyboard>
<DoubleAnimation Duration="0:0:1" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse" />
<DoubleAnimation Duration="0:0:1" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="rectangle" />
</Storyboard>
</VisualState>
<VisualState x:Name="RectangleState">
<Storyboard>
<DoubleAnimation Duration="0:0:1" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse" />
<DoubleAnimation Duration="0:0:1" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="rectangle"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="rectangle" Fill="Blue" Height="90" Margin="2,2,2,2" Opacity="0" Stroke="Black" VerticalAlignment="Top"/>
<Ellipse x:Name="ellipse" Fill="Orange" Height="90" Margin="2,2,2,2" Opacity="1" Stroke="Black" VerticalAlignment="Top"/>
</Grid>
</ControlTemplate>
</Window.Resources>
上面是一個在ControlTemplate中定義VisualStateManager.VisualStateGroups的例子,在應用程序中,控件之外定義也非常類似,下面的例子,在一個Button上隨着鼠標的Enter和Leave定義兩種狀態,根據不同的狀態來顯示不同顏色:Orange或Blue。
<Grid>
<Button Template="{StaticResource ButtonColorChangeStyleTemplate}" Content="Button"
HorizontalAlignment="Left" Margin="39,17,0,0" Name="button1" VerticalAlignment="Top" Width="104" Height="95" />
<Button Name="button2" MouseEnter="ColorChangeMouseEvent" MouseLeave="ColorChangeMouseEvent" Margin="198,17,0,0" Height="88" VerticalAlignment="Top" HorizontalAlignment="Left" Width="100">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MouseStates">
<VisualState Name="BlueState">
<Storyboard>
<ColorAnimation To="Blue" Storyboard.TargetName="rectBrush" Storyboard.TargetProperty="Color"/>
</Storyboard>
</VisualState>
<VisualState Name="OrangeState">
<Storyboard>
<ColorAnimation To="Orange" Storyboard.TargetName="rectBrush" Storyboard.TargetProperty="Color"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Button.Background>
<SolidColorBrush x:Name="rectBrush" Color="Orange"/>
</Button.Background>
</Button>
</Grid>
狀態遷移方法GoToState,GoToElementState
分別在ControlTemplate和應用程序中定義好了狀態,我們來看一下狀態遷移的方法:
1. GoToState, 用於轉換定義在控件自身內部定義的狀態(比較常見的標志就是狀態定義在控件的ControlTemplate中)。
2. GoToElementState,用於轉換由應用程序(而非控件自身)定義的狀態。
對於這兩種方法,VisualStateManager都執行相應邏輯,啟動和停止與有關狀態相關的StoryBoards,該邏輯是定義在默認的VisualStateManager的GoToStateCore方法中的,默認邏輯流程如下以GoToState為例(我們用舊State,新State表示狀態的遷移,方便描述):
1. 如果控件要轉換到的新State有Storyboard,運行該Storyboard。如果控件的舊State有Storyboard,結束其運行。
2. 如果控件已處於新state狀態(即新舊State相同),則GoToState不執行任何操作並返回true。
3. 如果新State在控件的ControlTemplate中不存在(因為是以GoToState為例,所以會去找ControlTemplate中的狀態),則GoToState不執行任何操作並返回 false。
有了上面狀態的定義,我們在C# Code中加入狀態遷移的邏輯,演示GoToState,GoToElementState的用法。
private void ShapeChangeMouseEvent(object sender, MouseEventArgs e)
{
if (button1.IsMouseOver)
{
bool result = VisualStateManager.GoToState(button1, "RectangleState", true);
}
else
{
VisualStateManager.GoToState(button1, "EllipseState", true);
}
}
private void ColorChangeMouseEvent(object sender, MouseEventArgs e)
{
if (button2.IsMouseOver)
{
VisualStateManager.GoToElementState(button2, "BlueState", true);
}
else
{
VisualStateManager.GoToElementState(button2, "OrangeState", true);
}
}
程序運行結果:Button會根據鼠標的Enter和Leave改變形狀和顏色。
提示1:注意分清GoToState,GoToElementState的使用場合。
提示2:當狀態被成功改變時,方法返回true,否則false。要好好的應用這幾個方法的返回值,因為一般情況下狀態遷移的多沒有明顯的Exception拋出,更多的是storyboard沒有被執行,UI沒有任何反映,默默的出錯(汗...)。應用返回值,可以得到狀態是否成功遷移,有利於判斷是我們storyboard邏輯問題,還是狀態遷移問題等等。
提示3:如果需要和默認流程不一樣的狀態轉換邏輯流程,我們可以繼承VisualStateManager實現我們自己的VisualStateManager,並且重寫 GoToStateCore方法。
