WPF如何使用颜色动画继承样式以更改颜色

时间:2013-06-16 21:26:03

标签: wpf xaml

我想创建一个通用的Button样式,其中包含重新定义的模板和动画,用于在鼠标输入,输出,向上,向下以及禁用和启用状态之间进行转换。这不是问题,但我想制作另一种按钮样式,除了背景颜色外基本相同。

我在样式资源和Storyboard中为 Normal Hover Disabled 状态定义了颜色:

<Style.Resources>
    <Color x:Key="DisabledBackground">#4c4c4c</Color>
    <Color x:Key="NormalBackground">#538ce1</Color>
    <Color x:Key="HoverBackground">#6ea8ff</Color>

    <Storyboard x:Key="MouseOverAnimation">
        <ColorAnimation Storyboard.TargetName="BackgroundBrush" 
                        Storyboard.TargetProperty="Color"
                        To="{StaticResource HoverBackground}"
                        Duration="0:0:0.3" />

        <DoubleAnimation Storyboard.TargetName="Underlay"
                         Storyboard.TargetProperty="Opacity"
                         To="0.7"
                         Duration="0:0:0.3" />
    </Storyboard>

    <!-- and few others... -->
</Style>

然后我有自定义模板,最后是ControlTemplate.Triggers部分:

<Trigger Property="IsMouseOver" Value="True">
    <Trigger.EnterActions>
        <BeginStoryboard Storyboard="{DynamicResource MouseOverAnimation}"/>
    </Trigger.EnterActions>

    <Trigger.ExitActions>
        <BeginStoryboard Storyboard="{DynamicResource MouseOutAnimation}"/>
    </Trigger.ExitActions>
</Trigger>

<!-- and few others... -->

现在我想要的是创建新样式,只需更改DisabledBackgroundNormalBackground的颜色:

<Style x:Key="Start"
       TargetType="{x:Type Button}"
       BasedOn="{StaticResource {x:Type Button}}">

    <Style.Resources>
        <Color x:Key="DisabledBackground">#4c4c4c</Color>
        <Color x:Key="NormalBackground">#960a0a</Color>
        <Color x:Key="HoverBackground">#de1111</Color>
    </Style.Resources>
</Style>

让控件模板不受影响。您可能已经注意到我在我的常用按钮样式中使用DynamicResource来引用样式资源中的故事板,这些样式资源以异常结束,因为故事板不能具有绑定或动态资源。这是我的最后一个“解决方案”,它不起作用,但我无法想出其他任何东西。

我不想复制和粘贴整个按钮样式只是为了更改两个颜色。如何修改我的风格,能够“动态”更改故事板动画中使用的颜色,或者至少继承样式并在那里设置颜色?

完成XAML

<Style TargetType="{x:Type Button}">

    <Style.Resources>
        <Color x:Key="DisabledBackground">#4c4c4c</Color>
        <Color x:Key="NormalBackground">#538ce1</Color>
        <Color x:Key="HoverBackground">#6ea8ff</Color>

        <Storyboard x:Key="MouseOverAnimation">
            <ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" To="{StaticResource HoverBackground}" Duration="0:0:0.3" />
            <DoubleAnimation Storyboard.TargetName="Underlay" Storyboard.TargetProperty="Opacity" To="0.7" Duration="0:0:0.3" />
        </Storyboard>
        <Storyboard x:Key="MouseOutAnimation" FillBehavior="Stop">
            <ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" To="{StaticResource NormalBackground}" Duration="0:0:0.3" />
            <DoubleAnimation Storyboard.TargetName="Underlay" Storyboard.TargetProperty="Opacity" To="0.2" Duration="0:0:0.3" />
        </Storyboard>
        <Storyboard x:Key="MouseDownAnimation">
            <DoubleAnimation Storyboard.TargetName="OverlayGradient" Storyboard.TargetProperty="Opacity" To="0.45" Duration="0:0:0.1" />
        </Storyboard>
        <Storyboard x:Key="MouseUpAnimation" Storyboard.TargetProperty="Background" FillBehavior="Stop">
            <DoubleAnimation Storyboard.TargetName="OverlayGradient" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.1" />
        </Storyboard>
        <Storyboard x:Key="DisabledAnimation">
            <ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" To="{StaticResource DisabledBackground}" Duration="0:0:0.3" />
            <ColorAnimation Storyboard.TargetName="UnderlayFillBrush" Storyboard.TargetProperty="Color" To="{StaticResource DisabledBackground}" Duration="0:0:0.3" />
        </Storyboard>
        <Storyboard x:Key="EnabledAnimation">
            <ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" To="{StaticResource NormalBackground}" Duration="0:0:0.3" />
            <ColorAnimation Storyboard.TargetName="UnderlayFillBrush" Storyboard.TargetProperty="Color" To="{StaticResource NormalBackground}" Duration="0:0:0.3" />
        </Storyboard>
    </Style.Resources>

    <Setter Property="Template">
        <Setter.Value>

            <ControlTemplate TargetType="Button">

                <Grid>

                    <!-- Button underlay glow

                    -->
                    <Rectangle x:Name="Underlay" Opacity="0.2">
                        <Rectangle.Fill>
                            <SolidColorBrush x:Name="UnderlayFillBrush" Color="{DynamicResource NormalBackground}"/>
                        </Rectangle.Fill>

                        <Rectangle.Effect>
                            <BlurEffect Radius="35" KernelType="Gaussian"/>
                        </Rectangle.Effect>
                    </Rectangle>

                    <!-- Button base border with rounded corners

                    Contains base background
                    -->
                    <Border x:Name="ButtonBackground" BorderThickness="1" CornerRadius="2">
                        <Border.BorderBrush>
                            <SolidColorBrush Color="Black" Opacity="0.8"/>
                        </Border.BorderBrush>

                        <Border.Background>
                            <SolidColorBrush x:Name="BackgroundBrush" Color="{DynamicResource NormalBackground}"/>
                        </Border.Background>

                        <!-- Button Overlay

                        Adds the background overlay gradient -->
                        <Border CornerRadius="2">
                            <Border.Background>
                                <LinearGradientBrush x:Name="OverlayGradient" Opacity="0.5" StartPoint="0,0" EndPoint="0,1">
                                    <GradientStop Offset="0" Color="White"/>
                                    <GradientStop Offset="0.02" Color="White"/>
                                    <GradientStop Offset="0.02" Color="Transparent"/>
                                    <GradientStop Offset="0.85" Color="#000000" />
                                </LinearGradientBrush>
                            </Border.Background>


                            <Border BorderThickness="1" CornerRadius="2">
                                <Border.BorderBrush>
                                    <SolidColorBrush Color="#b4b4b4" Opacity="0.2"/>
                                </Border.BorderBrush>

                                <!-- Inner text -->
                                <TextBlock Text="{TemplateBinding Content}"
                                           FontSize="{TemplateBinding FontSize}"
                                           FontFamily="Segoe UI"
                                           Foreground="White"
                                           TextWrapping="Wrap"
                                           HorizontalAlignment="Center"
                                           VerticalAlignment="Center"
                                           TextOptions.TextFormattingMode="Display"
                                           RenderOptions.BitmapScalingMode="NearestNeighbor">
                                    <TextBlock.Effect>
                                        <DropShadowEffect ShadowDepth="0" BlurRadius="6" Color="Black" RenderingBias="Quality"/>
                                    </TextBlock.Effect>
                                </TextBlock>

                            </Border>

                        </Border>

                    </Border>

                </Grid>

                <ControlTemplate.Triggers>

                    <Trigger Property="IsEnabled" Value="False">
                        <Trigger.EnterActions>
                            <BeginStoryboard Storyboard="{DynamicResource DisabledAnimation}"/>
                        </Trigger.EnterActions>

                        <Trigger.ExitActions>
                            <BeginStoryboard Storyboard="{DynamicResource EnabledAnimation}"/>
                        </Trigger.ExitActions>
                    </Trigger>

                    <Trigger Property="IsMouseOver" Value="True">
                        <Trigger.EnterActions>
                            <BeginStoryboard Storyboard="{DynamicResource MouseOverAnimation}"/>
                        </Trigger.EnterActions>

                        <Trigger.ExitActions>
                            <BeginStoryboard Storyboard="{DynamicResource MouseOutAnimation}"/>
                        </Trigger.ExitActions>
                    </Trigger>

                    <Trigger Property="IsPressed" Value="True">
                        <Trigger.EnterActions>
                            <BeginStoryboard Storyboard="{DynamicResource MouseDownAnimation}"/>
                        </Trigger.EnterActions>

                        <Trigger.ExitActions>
                            <BeginStoryboard Storyboard="{DynamicResource MouseUpAnimation}"/>
                        </Trigger.ExitActions>
                    </Trigger>

                </ControlTemplate.Triggers>

            </ControlTemplate>


        </Setter.Value>
    </Setter>

</Style>

2 个答案:

答案 0 :(得分:2)

引用MSDN上的文档:

  

您不能使用动态资源引用或数据绑定表达式   设置Storyboard或动画属性值。那是因为   Style中的所有内容都必须是线程安全的,并且是计时系统   必须冻结Storyboard对象才能使它们具有线程安全性。故事板   如果它或其子时间轴包含动态资源,则不能被冻结   引用或数据绑定表达式。有关的更多信息   冻结和其他Freezable功能,请参阅Freezable Objects Overview

或者使用以下hack:

http://objectmix.com/csharp/733011-wpf-using-storyboards-custom-control.html

在那里,创建从 Button 控件继承的元素,以解决此限制(不在样式或模板中引入任何Freezable对象)。

你当然可以使用超级黑客等,但在我看来,在我看来,在资源中使用不同的风格或颜色会更容易。这不会那么困难。

有关详细信息,请参阅:

MSDN docs

Similar question

答案 1 :(得分:0)

尽管问了这个问题已经有好几年了,但我有一些有用的信息可供其他人记住,因此您不会像几年前尝试解决此问题时犯同样的错误。

TLDR 在使用 ComponentResourceKey 作为使动态资源静态化的一种方式时要非常谨慎,以便可以将其与freezables一起使用。它可以编译,甚至可以运行,但是您很幸运,可能甚至不应该放在第一位,因为它可以避免人们从悬崖上走下来。请参阅底部的代码示例,请不要使用 freezables

详细信息 像其他人一样,我想让我的资源尽可能地灵活。我知道我们不能将动态资源用于动画,而是通过类似于以下示例的代码以一种“聪明”的方式解决了这一问题。这里的键(没有双关语)是使用 ComponentResourceKey 。在大约一年前使用这种方法后,当它与动画配合使用时,我几乎快要在地板上跳霹雳舞了。乍一看,您可能会想,“当然行得通,您在每个代码示例中都使用了静态资源” 。但是,一旦您意识到 ComponentResourceKey 是一个标记扩展,它提供了“超级功能”,使您可以引用当前程序集中甚至不存在的资源。您不必将该程序集作为参考,可以更容易地理解它可以有效地转换为伪动态资源。

对于Freezable,我认为这当时对我有用,因为我不小心创建了一个初始化命令,使它看起来很有效。这是在首次使用之前定义的值。然后在这个周末(以及现在以后的几年)里,我开始将所有bin / obj和ext文件转储到一个公共位置,在这些位置它们很容易被我的构建系统破坏,因此我将感到自己有一个干净的构建。突然,我开始遇到致命的xaml异常,这表明未定义在某些位置使用的ComponentResourceKey。然后,我直接将程序集包括在使用它们的位置,而不是直接包含在主exe中。我知道这与我要避免的方式(耦合)背道而驰,但是那是在午夜之后,我希望它在退休之前进行编译,但仍然会因为相同的XAML异常而崩溃。

我克服了

  

它工作了一年多了,一定是我改变了   最近

我们所有人都有这种感觉,很明显,一天之内我就根本不应该使用我所做的事情。我认为我们中的“优秀程序员”总是希望尽可能地灵活和脱钩,即使这意味着想出一些真正有创造力的技术来满足我们解决面部问题的需要。天。

  

除了真正破坏构建之间的二进制文件外,我还   将128个库转换成Nuget包,我认为   以某种方式影响了键值时的初始化顺序   设置与使用时间。午夜后一切都像伏都教。

我仍然认为,使用 ComponentResourceKey 标记扩展名可能是解耦xaml库相互依赖关系的一种好方法,您只是无法将它们与情节提要动画中的可冻结对象安全地使用。回到我最初使用这项技术时,我并没有尝试在运行时动态更改值,因为对我而言,这只是一个扩展目标,我知道有一天我会回头,很高兴我花时间将其设置为如此灵活。 (现在我希望我有时间回来!)

使用 ComponentResourceKey 的示例 不要对 freezables

执行此操作
<BeginStoryboard>
    <Storyboard FillBehavior="Stop" Duration="0:0:.1">
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background">
            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type local:SettingKeys}, ResourceId=DefaultButtonClickBackgroundBrush}}" />
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</BeginStoryboard>

顺便说一句,这里是定义密钥的示例,以防您想将此技术应用于除freezable之外的任何事物。请注意,local:SettingKeys只是一个空的C Sharp类,请使用Google的技术来完全理解。

<SolidColorBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:SettingKeys},ResourceId=DefaultButtonClickBackgroundBrush}" Color="#FE8D00" />

如果这可以使其他人免于头痛,我会很高兴的。就像被接受的答案的作者所说,您可以使用创造性的技巧,但应避免使用它们,或像我一样走下悬崖。