WPF如何继承具有颜色动画的样式以改变颜色

3
我希望创建一个通用的按钮样式,重新定义模板和过渡动画,以便在鼠标进入、退出、按下、松开、禁用和启用状态之间进行转换。这不是问题,但我想制作另一种按钮样式,基本上与背景颜色不同。
我已经在样式资源和Storyboards中为正常悬停禁用状态定义了颜色。
<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来引用以样式资源结尾的Storyboard,因为Storyboard不能具有绑定或动态资源而导致异常。这是我最后的“解决方案”,但它不起作用,我想不出其他办法。

我不想复制和粘贴整个按钮样式来更改两个颜色。如何修改我的样式,以便能够“动态”更改Storyboard动画中使用的颜色,或者至少继承样式并在那里设置颜色?

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个回答

2
引用MSDN文档

无法使用动态资源引用或数据绑定表达式来设置Storyboard或animation属性值。因为Style中的所有内容都必须是线程安全的,并且时间系统必须冻结Storyboard对象以使它们线程安全。如果Storyboard或其子时间轴包含动态资源引用或数据绑定表达式,则无法冻结Storyboard。有关冻结和其他Freezable特性的更多信息,请参阅Freezable Objects Overview

您可以尝试使用各种超级黑科技等,但在我看来,更简单的方法是在资源中使用不同的样式或颜色。这将不会很困难。

更多信息请参见:

MSDN文档

类似问题


1
是的,我找到了这个引用,我认为继承样式并仅更改父样式的一部分(包括动画)必须是非常常见的操作。显然我错了,但感谢您的回答。 - RiZe
那些 MSDN 链接已经失效了。:( - Mike G

0

虽然这个问题已经被提出并回答了多年,但我有一些有用的信息要让其他人记住,以便您不会像我多年前尝试解决这个问题时犯同样的错误。

简而言之 非常小心地使用ComponentResourceKey作为一种使用动态资源静态化的方式,以便您可以将其与可冻结对象一起使用。它将编译,甚至可能运行,但您很幸运,它可能根本不应该被允许,因为它可以防止人们走下悬崖。请参见底部的代码示例,不要在其中使用freezables

全面细节 和其他人一样,我想尽可能地保持我的资源的灵活性。我知道我们不能使用动态资源来进行动画,但是发现了一个类似以下示例代码的“聪明”方法绕过这个问题。关键在于使用ComponentResourceKey。在我大约一年前使用这种方法后,当它与动画一起工作时,我几乎要跳 break dance 了。一开始看起来你可能会想,“嗯,当然这行得通,你根据代码示例使用了静态资源。”然而,一旦你意识到ComponentResourceKey是一个标记扩展,提供了“超级能力”,让你引用甚至不存在于当前程序集中的资源,并且你不必将该程序集包含在引用中,那么理解这实际上相当于伪动态资源就变得更容易了。

在可冻结对象的情况下,我相信这在当时对我起作用是因为我无意中创建了一个初始化顺序,使得它看起来可以工作。也就是说,在首次使用之前定义了该值。然后在这个周末(以及现在多年以后),我开始将所有的bin/obj和ext文件倾倒到一个公共位置,这样它们可以很容易地被我的构建系统覆盖,这样我就可以确信我有一个干净的构建。突然间,我开始遇到致命的xaml异常,指示我在某些位置使用的ComponentResourceKey未被定义。然后,我直接将这些程序集包含在它们被使用的地方,而不是在主exe中。我知道这违反了我想要避免的(耦合),但那时已经过了午夜,我希望在退休之前能编译通过,但它仍然崩溃并显示相同的XAML异常。

在我克服了

好吧,它已经运行了一年,一定是我最近改变的东西

作为程序员,我们总有一种感觉,清晰地认识到我所做的事情根本不应该被使用。我认为在我们内心深处,"好的程序员"总是希望尽可能灵活和解耦,即使这意味着采用一些非常创造性的技术,满足我们当天解决问题的需求。

除了真正摧毁二进制文件之间的差异,我还将大约128个库转换为Nuget包,并且我认为这影响了键的值在何时设置与使用时的初始化顺序。在午夜之后,这一切都变得像巫术一样。

我仍然认为使用组件资源密钥标记扩展可以是一种很好的解耦XAML库相互依赖性的方法,只是你不能安全地在故事板动画中使用可冻结对象。当我第一次应用这种技术时,我没有尝试在运行时动态更改值,因为对我来说这只是一个伸展目标,我知道总有一天我会回头看,很高兴我花时间让它变得那么灵活。(现在我希望我能拥有那些时间!)

使用ComponentResourceKey的实例 不要在可冻结对象中这样做

<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>

顺便提一下,这里有一个定义密钥的示例,以防您想将此技术应用于除了可冻结对象之外的任何内容。请注意,local:SettingKeys只是一个空的C#类,请搜索该技术以充分理解。

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

如果这能帮助其他人避免头疼,我会很高兴。正如被接受的答案的作者所述,您可以使用创造性的黑客技巧,但应避免它们,否则就像我一样走向悬崖。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接