原文:The VisualStateManager and Triggers
Author:Carole Snyder
Silverlight 推出了可视化状态管理器( Visual State Manager ),它使得控件模版作者更方便地指定依赖可视化状态的控件外观。WPF Toolkit 附带了一个可视化状态管理器( Visual State Manager ),WPF 的下个版本将会加入 VSM。可以理解地,VisualStateManager 的引进带来了问题:何时使用 VSM 代替触发器,何时使用触发器会更适合。本博文尝试解决这个问题:更多对如何使用可视化状态管理器( VisualStateManager )的深入讨论在这里。
可视化状态管理器( VisualStateManager )支持 parts 和 states 模型,控件作者使可视化状态在控件模版中形式化的一种方法。可视化状态管理器( VisualStateManager )使控件作者能够管理一个控件的不同状态并且为设计工具提供了一个方法,比如 Microsoft Blend 支持通过控件的可视化状态自定义它的外观。在 parts 和 states 模型被引进之前,普遍地,当它改变了可视化状态,控件模版作者使用触发器来改变控件的外观。下面的控件模版中,当鼠标悬停在按钮上面或者按钮被按压时,使用触发器来改变按钮的 border 颜色。
<ControlTemplate TargetType="Button"> <Grid> <Ellipse Fill="{TemplateBinding BorderBrush}"/> <Ellipse x:Name="ButtonShape" Margin="5" Fill="{TemplateBinding Background}"/> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" Value="Cyan"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="BorderBrush" Value="Red"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
在这个模型中,没有关于什么是可视化状态正式的协议,这里只有定义在控件里的一些用来改变控件外观的属性。 A control that follows the parts and control model proactively communicates its visual states to control template authors. 当一个控件使用可视化状态管理器( VisualStateManager )来改变它的可视化状态,它就期望 控件模版(ControlTemplate )使用 可视化状态管理器( VisualStateManager )来为一个给定的可视化状态指定控件的外观。控件模版作者也可以通过可视化过渡(VisualTransitions)使用自定义可视化状态之间的过渡。可视化过渡(VisualTransitions)使得空间模版作者能够通过改变单个属性改变的时间和区间对单个过渡(transition)进行微调,甚至对在其他状态中没有提及的属性加上动画效果(animate)。下面的例子使用可视化状态管理器( VisualStateManager )而不是触发器来指定控件外观的改变。
<ControlTemplate TargetType="Button"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualStateGroup.Transitions> <!--Take one half second to transition to the MouseOver state.--> <VisualTransition To="MouseOver" GeneratedDuration="0:0:0.5" /> </VisualStateGroup.Transitions> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Duration="0" Storyboard.TargetName="borderColor" Storyboard.TargetProperty="Color" To="Cyan"/> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation Duration="0" Storyboard.TargetName="borderColor" Storyboard.TargetProperty="Color" To="Red"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Ellipse> <Ellipse.Fill> <SolidColorBrush x:Name="borderColor" Color="Black"/> </Ellipse.Fill> </Ellipse> <Ellipse x:Name="ButtonShape" Margin="5" Fill="{TemplateBinding Background}"/> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </ControlTemplate>
控件通过使用可视化状态管理器( VisualStateManager )并调用 VisualStateManager.GoToState 来改变可视化状态。当这一切发生时,在可视化状态(VisualState)和可视化过渡(VisualTransition)对象中, 可视化状态管理器( VisualStateManager )正确地停止和开始故事板(storyboards),因此按钮的外观正确地改变可视化状态。为此,这里有一个明确的分工:控件作者指定控件的哪个可视化状态并决定一个控件何时进入每一个可视化状态;模版作者指定这个控件在每个可视化状态中的外观。
然而触发器在WPF中并不是没用了。你可以使用触发器改变控件的外观,这并不对应与一个可视化状态。举个例子,按钮有一个 IsDefault 属性,但是在按钮中没有一个相对应的可视化状态。控件模版作者可能想要指定依赖 IsDefault 的值的按钮外观,这是一个适合用触发器的情景。下面的例子重复了之前的例子,并加入了一个触发器来指定依赖 IsDefault 的值的按钮外观。
<ControlTemplate TargetType="Button"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualStateGroup.Transitions> <!--Take one half second to transition to the MouseOver state.--> <VisualTransition To="MouseOver" GeneratedDuration="0:0:0.5" /> </VisualStateGroup.Transitions> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Duration="0" Storyboard.TargetName="borderColor" Storyboard.TargetProperty="Color" To="Cyan"/> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation Duration="0" Storyboard.TargetName="borderColor" Storyboard.TargetProperty="Color" To="Red"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Ellipse> <Ellipse.Fill> <SolidColorBrush x:Name="borderColor" Color="Black"/> </Ellipse.Fill> </Ellipse> <Ellipse x:Name="defaultOutline" Stroke="{TemplateBinding Background}" StrokeThickness="2" Margin="2"/> <Ellipse x:Name="ButtonShape" Margin="5" Fill="{TemplateBinding Background}"/> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsDefault" Value="False"> <Setter TargetName="defaultOutline" Property="Stroke" Value="Transparent"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
按钮中的 IsMouseOver 和 IsPressed 属性依然有效,但是你不应该创建触发器来阻止它们改变按钮的外观。这并不意味这些属性没有用了。这些属性依然可以被应用作者用来在代码中检查控件的可视化状态,甚至当他们使用可视化状态管理器(VisualStateManager)在它们的可视化状态之间过渡时,控件作者应该继续定义用于可视化状态的属性。