65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (9投票s)

2011年5月5日

CPOL

4分钟阅读

viewsIcon

23771

downloadIcon

426

实现一个控件,用于支持使用 VisualStateManager 更改 Silverlight Shape 的颜色

引言

在 Silverlight 中,可以使用 XAML 中的 VisualStateManager 来定义动画。但是,这项技术存在一些显著的缺点,尤其是在尝试创建可重用控件时。另外,最好将视图的定义保留在代码或 XAML 中。两者都包含会使维护更加困难。理想情况下,应尽可能多地在 XAML 中完成,这也是创建 XAML 而非坚持使用 WinForms 技术的原因。

背景

我目前工作的团队有几个人负责用户界面设计。他们广泛使用了仅由图标组成的按钮。这些图标具有鼠标悬停和按下状态的行为。我最初要处理的图标由单个路径定义,并且只有一个颜色变化。这意味着路径的填充必须根据 VisualStateManagerCommonStatesMouseOverPressed 来改变。最初的解决方案是为每个用作 Button 的不同图标创建控件模板。这非常不理想,因为它意味着许多更改将需要修改所有或许多按钮。然后,ButtonToggleButton 控件可能使用相同的图标。我的初始构想是创建一个 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>

起初这看起来是正确的解决方案,但它并没有处理所有的图标按钮。

 

第一个识别出的问题是当存在多个路径时。为了解决这种情况,我的一位同事创建了一个控件,该控件基本上管理了 ButtonControlTemplate 中的所有内容。它不仅处理了多个路径的颜色,还可以指定哪些路径会受到影响,还可以应用渲染变换。它似乎处理了我们需要的所有按钮行为。

然后我遇到了需要多个颜色变化的情况,而 ControlTemplate 并没有处理多个颜色。我或许可以扩展现有的 ControlTemplate 来处理多个颜色,但我并不喜欢这个概念。基本上,我们是在提供 VisualStateManager 的另一个实现。VisualStateManager 是 Silverlight 中的一个标准,Silverlight 开发人员都理解它。它还提供了对已定义行为的非常明确的定义。不幸的是,Silverlight 不允许你为控件任意创建 DependencyProperties,甚至连 ContentlPresenter 也不行。在我看来,最好创建一个 ContentControl,其中包含我创建所需行为所需的属性。

因此,我创建了一个包含两个新的 DependencyProperties 的控件:Color1Color2。当控件初始化或内容更改时,将使用 VisualTreeHelper.GetChild 方法扫描 Content 中的控件。要通过 VisualStateManager 更改的每个控件都会设置一个 DependencyProperty,该属性包含一个 string,指示控件(Shape)的填充颜色将由 Color1Color2 DependencyProperty 控制,并将被放入与该 Color 属性关联的 List 中。

Color1Color2 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 日:初始发布
 

 

© . All rights reserved.