支持使用 VisualStateManager 的自定义动画的控件






4.89/5 (9投票s)
实现一个控件,用于支持使用 VisualStateManager 更改 Silverlight Shape 的颜色
引言
在 Silverlight 中,可以使用 XAML 中的 VisualStateManager
来定义动画。但是,这项技术存在一些显著的缺点,尤其是在尝试创建可重用控件时。另外,最好将视图的定义保留在代码或 XAML 中。两者都包含会使维护更加困难。理想情况下,应尽可能多地在 XAML 中完成,这也是创建 XAML 而非坚持使用 WinForms 技术的原因。
背景
我目前工作的团队有几个人负责用户界面设计。他们广泛使用了仅由图标组成的按钮。这些图标具有鼠标悬停和按下状态的行为。我最初要处理的图标由单个路径定义,并且只有一个颜色变化。这意味着路径的填充必须根据 VisualStateManager
的 CommonStates
、MouseOver
和 Pressed
来改变。最初的解决方案是为每个用作 Button 的不同图标创建控件模板。这非常不理想,因为它意味着许多更改将需要修改所有或许多按钮。然后,Button
和 ToggleButton
控件可能使用相同的图标。我的初始构想是创建一个 ControlTemplate
,它接受一个 Content
,该 Content
是一个 string
,表示 Path
控件的值,并且 Fill
定义为 Foreground
。
<Style x:Key="PathButton" TargetType="Button"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Grid Background="Transparent"> <Path Name="ButtonPath" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" Data="{TemplateBinding Content}" /> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation Duration="0" Storyboard.TargetName="ButtonPath" Storyboard.TargetProperty="(Shape.Fill) .(SolidColorBrush.Color)" To="{StaticResource AccentColor}" /> </Storyboard> </VisualState> <VisualState x:Name="MouseOver" /> <VisualState x:Name="Disabled" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
起初这看起来是正确的解决方案,但它并没有处理所有的图标按钮。
第一个识别出的问题是当存在多个路径时。为了解决这种情况,我的一位同事创建了一个控件,该控件基本上管理了 Button
的 ControlTemplate
中的所有内容。它不仅处理了多个路径的颜色,还可以指定哪些路径会受到影响,还可以应用渲染变换。它似乎处理了我们需要的所有按钮行为。
然后我遇到了需要多个颜色变化的情况,而 ControlTemplate
并没有处理多个颜色。我或许可以扩展现有的 ControlTemplate
来处理多个颜色,但我并不喜欢这个概念。基本上,我们是在提供 VisualStateManager
的另一个实现。VisualStateManager
是 Silverlight 中的一个标准,Silverlight 开发人员都理解它。它还提供了对已定义行为的非常明确的定义。不幸的是,Silverlight 不允许你为控件任意创建 DependencyProperties
,甚至连 ContentlPresenter
也不行。在我看来,最好创建一个 ContentControl
,其中包含我创建所需行为所需的属性。
因此,我创建了一个包含两个新的 DependencyProperties
的控件:Color1
和 Color2
。当控件初始化或内容更改时,将使用 VisualTreeHelper.GetChild
方法扫描 Content 中的控件。要通过 VisualStateManager
更改的每个控件都会设置一个 DependencyProperty
,该属性包含一个 string
,指示控件(Shape
)的填充颜色将由 Color1
或 Color2
DependencyProperty
控制,并将被放入与该 Color
属性关联的 List
中。
Color1
和 Color2
DependencyProperties
各自都有一个更改事件处理程序,该处理程序将更新其关联列表中的所有控件,以便将填充设置为具有新颜色的 SolidColorBrush
。
Using the Code
对于熟悉使用 VisualStateManager
的人来说,使用这个控件实际上非常简单。不幸的是,必须创建一个 ControlTemplate
来包含 VisualStateManager
的 XAML。在本例中,我使用的是 Button
的控件。
<Style x:Key="TwoColorChangeButton" TargetType="Button">
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="TabNavigation" Value="Local" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1">
<VisualTransition.GeneratedEasingFunction>
<QuadraticEase EasingMode="EaseOut" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0"
To="LightGreen" Storyboard
.TargetProperty="Color1"
Storyboard.TargetName="ContentControl" />
<ColorAnimation Duration="0"
To="Black" Storyboard.TargetProperty="Color2"
Storyboard.TargetName="ContentControl" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0"
To="Red" Storyboard.TargetProperty="Color1"
Storyboard.TargetName="ContentControl" />
<ColorAnimation Duration="0" To="Green"
Storyboard.TargetProperty="Color2"
Storyboard.TargetName="ContentControl" />
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimation Duration="0"
To="LightGray"
Storyboard.TargetProperty="Color1"
Storyboard.TargetName="ContentControl" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<views:ColorAdapterControl x:Name="ContentControl"
Color2="White"
Color1="Black"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>Buttons
您可以直接在内容中编码路径,但由于这是更大项目的一部分,因此我在 Grid.Resources
中定义了路径。
<ControlTemplate x:Key="ButtonContent_Plus2" T argetType="ContentControl"> <Canvas Width="12.1092" Height="12.1096"> <Path Width="12.1092" Height="12.1096" Canvas.Left="0.390177" Canvas.Top="0.391614" Stretch="Fill" views:ColorAdapterExt.FillColorSelector="Color1" Data="F1 M 7.25134,0.446381C 10.5649,0.892731 12.8903,3.94067 12.4446, 7.25317C 11.9994,10.5666 8.95322,12.8927 5.63794,12.4463C 2.32419, 12.0019 0,8.95447 0.444702,5.64102C 0.890839,2.32654 3.93753, 0.000457764 7.25134,0.446381" /> <Path Width="5.63623" Height="5.6366" Canvas.Left="3.63184" Canvas.Top="3.6283" Stretch="Fill" views:ColorAdapterExt.FillColorSelector="Color2" Data="F1 M 6.99142,9.26489L 5.90842,9.26489L 5.90842,6.98758L 3.63184, 6.98758L 3.63184,5.90463L 5.90842,5.90463L 5.90842,3.6283L 6.99142, 3.6283L 6.99142,5.90463L 9.26807,5.90463L 9.26807, 6.98758L 6.99142,6.98758L 6.99142,9.26489 Z " /> </Canvas> </ControlTemplate>
然后在 Silverlight 页面中,我只需要以下 XAML 即可创建我的按钮:
<Button x:Name="SelectButton" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource TwoColorChangeButton}" Background="Transparent"> <ContentControl Template="{StaticResource ButtonContent_Plus2}" /> <Button.RenderTransform> <ScaleTransform ScaleX="4" ScaleY="4" /> </Button.RenderTransform> </Button>
关注点
我只使用了两种颜色,并且只更改了路径的 Fill
颜色。代码的设置方式是,可以使用内容中的任何 Shape
。此技术可用于许多不同的 DependencyProperties
的任意数量的值。一个例子是控制 Grid
中某行的高度或某列的宽度。这些属性的类型是 GridLength
,因此无法通过动画控制,但通过这样的适配器,这是可能的。
历史
- 2011 年 5 月 5 日:初始发布