【问题标题】:WPF How to inherit style with color animation to change the colorWPF如何用颜色动画继承样式来改变颜色
【发布时间】:2013-06-16 21:26:03
【问题描述】:

我想创建一个通用的Button 样式,其中包含重新定义的模板和动画,用于在鼠标移入、移出、向上、向下以及禁用和启用状态之间进行转换。没问题,不过我想再做一个按钮样式,除了背景颜色基本一样。

我在样式资源和情节提要中为 NormalHoverDisabled 状态定义了颜色:

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

【问题讨论】:

    标签: wpf xaml


    【解决方案1】:

    引用MSDN上的文档:

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

    您当然可以使用超级黑客等,但就您而言,在我看来,在资源中使用不同的样式或颜色更容易。不会那么难的。

    更多信息见:

    MSDN docs

    Similar question

    【讨论】:

    • 是的,我找到了这句话,但我认为继承样式和仅更改部分父样式(包括动画)必须是非常常见的操作。显然我错了,但谢谢你的回答。
    • 那些 MSDN 链接已失效。 :(
    【解决方案2】:

    尽管这个问题被问及回答已经有好几年了,但我有一些有用的信息要记住,这样你就不会犯我多年前在尝试解决这个问题时犯的同样错误。

    TLDR 在使用 ComponentResourceKey 时要非常谨慎,以使您的动态资源成为静态资源,以便您可以将其与可冻结对象一起使用。它会编译,它甚至可以运行,但你很幸运,而且它可能一开始就不应该被允许,因为它可以防止人们从悬崖上走下来。请参阅底部的代码示例,不要使用 freezables

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

    在可冻结的情况下,我相信当时这对我有用,因为我不小心创建了一个初始化排序,使它看起来有效。也就是说,该值是在首次使用之前定义的。然后在这个周末(现在是几年后),我开始将我所有的 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>
    

    顺便说一句,这是一个定义密钥的示例,以防您想将此技术应用于除可冻结文件以外的任何内容。注意 local:SettingKeys 只是一个空的 C Sharp 类,google 一下技术就完全明白了。

    &lt;SolidColorBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:SettingKeys},ResourceId=DefaultButtonClickBackgroundBrush}" Color="#FE8D00" /&gt;

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

    【讨论】:

      猜你喜欢
      • 2010-12-12
      • 1970-01-01
      • 2012-03-29
      • 2019-12-22
      • 1970-01-01
      • 2015-02-06
      • 2011-06-29
      • 2013-08-15
      • 1970-01-01
      相关资源
      最近更新 更多