有没有办法更改在 Base-Style 中定义的 ControlTemplate 的特定属性,而无需复制粘贴整个代码? [英] Is there a way to change specific properties of a ControlTemplate defined in a Base-Style, without having to copy-paste the entire code?

查看:19
本文介绍了有没有办法更改在 Base-Style 中定义的 ControlTemplate 的特定属性,而无需复制粘贴整个代码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为两个按钮创建一个样式,除了颜色之外,它们都具有完全相同的属性.请参阅此图片以供参考.

I want to create a style for two Buttons, with both of them having exactly the same properties, except for their colors. See this image for reference.

理想情况下,我想为这些按钮创建一个共享 ControlTemplate 的 Base-Style,然后以某种方式切换颜色.

Ideally, I would like to create a Base-Style with a shared ControlTemplate for these Buttons once and then switch out the colors in some way.

我最初的想法是创建上述 Base-Style,它使用先前定义的 SolidColorBrush 资源作为颜色,然后添加两个额外的样式(Accent1Button-Style 和 Accent2Button-Style)来覆盖这些 SolidColorBrush 资源.代码如下所示:

My initial thought was to create said Base-Style which uses previously defined SolidColorBrush resources for the colors and then add two additional styles (Accent1Button-Style and Accent2Button-Style) which overwrite these SolidColorBrush resources. The code would look like this:

<!-- Define color resources which are used by the Base-Button Style -->
<SolidColorBrush x:Key="ButtonBackgroundBrush" Color="{Binding Color, Source={StaticResource BaseMediumBrush}}" />
<SolidColorBrush x:Key="ButtonBackgroundHoverBrush" Color="{Binding Color, Source={StaticResource BaseHighBrush}}" />
<SolidColorBrush x:Key="ButtonBackgroundPressedBrush" Color="{Binding Color, Source={StaticResource BaseLowBrush}}" />
<SolidColorBrush x:Key="ButtonBackgroundDisabledBrush" Color="{Binding Color, Source={StaticResource BaseHighBrush}}" />

<SolidColorBrush x:Key="ButtonBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderHoverBrush" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderPressedBrush" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderDisabledBrush" Color="Transparent" />

<Style x:Key="StandardButton" TargetType="Button">
    <!-- Properties removed to shorten the code -->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="Bd"
                        CornerRadius="{StaticResource ButtonCornerRadius}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        Background="{TemplateBinding Background}"
                        Padding="{TemplateBinding Padding}">
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                      RecognizesAccessKey="True" />
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition GeneratedDuration="0:0:0.2" />
                                <VisualTransition GeneratedDuration="0" To="Disabled" />
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                                  Storyboard.TargetProperty="Background.Color">
                                        <EasingColorKeyFrame KeyTime="0" />
                                    </ColorAnimationUsingKeyFrames>
                                    <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                                  Storyboard.TargetProperty="BorderBrush.Color">
                                        <EasingColorKeyFrame KeyTime="0" Value="{Binding Color, Source={StaticResource ButtonBorderHoverBrush}}" />
                                    </ColorAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <!-- ... -->
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <!-- ... -->
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="Button" BasedOn="{StaticResource StandardButton}">
    <Style.Resources>
        <!-- Overwrite the above colors here -->
        <SolidColorBrush x:Key="ButtonBackgroundBrush" Color="{Binding Color, Source={StaticResource Accent1MediumBrush}}" />
        <SolidColorBrush x:Key="ButtonBackgroundHoverBrush" Color="{Binding Color, Source={StaticResource Accent1HighBrush}}" />
        <!-- And so on... -->
    </Style.Resources>
</Style>

但是,这不起作用,因为 WPF 不会覆盖资源.

However, this doesn't work, since WPF doesn't overwrite the resources.

另一种方法是创建一个提供附加属性的辅助类(例如 ColorHelper.HoverColor).我想过在模板中使用这个属性并在 Accent1/Accent2 样式中设置它,但这是不可能的,因为它需要 TemplateBindings,模板中使用的 ColorAnimation 不支持它.

Another approach was to create a helper-class which offers an attached property (e.g. ColorHelper.HoverColor). I thought about using this property in the template and setting it in the Accent1/Accent2 styles, but that is not possible, because it would require TemplateBindings, which is not supported by the ColorAnimation used in the template.

现在,我看到的唯一选择是复制/粘贴整个模板并在适当的位置(例如动画内部)切换颜色.但是,我想避免这种情况.

Right now, the only option I see is to copy/paste the whole template and switch out the colors at the appropriate places (inside the animation, for example). I would like to avoid that, however.

最后,有没有办法只定义我的 Base-Style 一次,同时创建单独的子样式",这些子样式"更改不属于 Button 类的模板特定属性(如 HoverColor, PressedColor, ...)?

So finally, is there a way to only define my Base-Style once, while also creating separate "sub-styles" which change template-specific properties that are not part of the Button-class (like the HoverColor, PressedColor, ...)?

澄清为什么带有附加属性的方法不起作用:假设我已经引入了一个具有以下附加属性的 ColorHelper 类:

To clarify why an approach with attached properties won't work: Assuming that I have introduced a class ColorHelper with the following attached property:

public static readonly DependencyProperty HoverColorProperty =
        DependencyProperty.RegisterAttached("HoverColor", typeof(Color), typeof(ColorHelper), new PropertyMetadata(Colors.Black));

public static Color GetHoverColor(DependencyObject obj)
{
    return (Color)obj.GetValue(HoverColorProperty);
}

public static void SetHoverColor(DependencyObject obj, Color value)
{
    obj.SetValue(HoverColorProperty, value);
}

理论上,我可以像这样在我的子样式中设置这个属性:

I could, in theory, set this property in my sub-styles like this:

<Style TargetType="Button" BasedOn="{StaticResource StandardButton}">
    <Setter Property="local:ColorHelper.HoverColor" Value="Red" />
</Style>

<Style TargetType="Button" BasedOn="{StaticResource StandardButton>}">
    <Setter Property="local:ColorHelper.HoverColor" Value="Blue" />
</Style>

这个解决方案的问题在于 Base-Style,但准确地说,在代码的以下部分:

The problem with this solution lies in the Base-Style though, to be precise, in the following part of the code:

<!-- snip -->
<VisualState x:Name="MouseOver">
    <Storyboard>
        <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd" Storyboard.TargetProperty="Background.Color">
            <EasingColorKeyFrame KeyTime="0" 
                                 Value="{Binding Path=(local:ColorHelper.HoverColor), RelativeSource={RelativeSource TemplatedParent}}" />
        </ColorAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>
<!-- snip -->

如您所见,EasingColorKeyFrame 的值通过 Binding 绑定到 TemplatedParent(我认为没有其他方法可以这样做).虽然 WPF 不支持这(因为 Freezeables 与动画一起工作的方式)并且颜色永远不会在动画中使用.我还在输出窗口中收到以下错误消息:System.Windows.Data 错误:2:找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement.绑定表达式:路径=(0);数据项=空;目标元素是EasingColorKeyFrame"(HashCode=8140857);目标属性是值"(类型颜色")

As you can see, the EasingColorKeyFrame's value is bound via a Binding to the TemplatedParent (and I see no other way for doing this). This isn't supported by WPF though (because of the way Freezeables work together with animations) and the color never gets used in the animation. I also get the following error message in my Output window: System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=(0); DataItem=null; target element is 'EasingColorKeyFrame' (HashCode=8140857); target property is 'Value' (type 'Color')

推荐答案

经过三天的寻找解决方案,我偶然发现了以下帖子:绑定到控件在 VisualStateManager 中的属性

After three days of looking for a solution, I stumbled upon the following post: Bind to Control's Property inside VisualStateManager

我可以通过对 BindingProxy 使用完全相同的方法来解决我的问题(我只需要从 DoubleAnimation 切换到 ColorAnimation).

I could solve my problem by using the exact same approach with the BindingProxy (I only needed to switch from the DoubleAnimation to the ColorAnimation).

一段时间后,我发现了一个更好的方法.当 BindingProxy 解决方案起作用时,将 Storyboard 实例简单地放入显示在可视化树中的元素的资源中要容易得多.通过这样做,Bindings 将能够找到 TemplatedParent.

After some time, I discovered a better way. While the BindingProxy solution is working, it is much easier to simply put the Storyboard instances into the resources of an element which appears in the visual tree. By doing this, the Bindings will be able to find the TemplatedParent.

请参阅此处的示例:

<Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ButtonBase">
                    <Grid>
                        <Grid.Resources>
                            <Storyboard x:Key="MouseOverStoryboard">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                              Storyboard.TargetProperty="Background.Color">
                                    <EasingColorKeyFrame KeyTime="0" Value="{Binding Path=(theming:MouseOverProperties.BackgroundColor), RelativeSource={RelativeSource TemplatedParent}}" />
                                </ColorAnimationUsingKeyFrames>
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                              Storyboard.TargetProperty="BorderBrush.Color">
                                    <EasingColorKeyFrame KeyTime="0" Value="{Binding Path=(theming:MouseOverProperties.BorderColor), RelativeSource={RelativeSource TemplatedParent}}" />
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>
                            <Storyboard x:Key="PressedStoryboard">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                              Storyboard.TargetProperty="Background.Color">
                                    <DiscreteColorKeyFrame KeyTime="0" Value="{Binding Path=(theming:MouseOverProperties.BackgroundColor), RelativeSource={RelativeSource TemplatedParent}}" />
                                </ColorAnimationUsingKeyFrames>
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                              Storyboard.TargetProperty="BorderBrush.Color">
                                    <DiscreteColorKeyFrame KeyTime="0" Value="{Binding Path=(theming:MouseOverProperties.BorderColor), RelativeSource={RelativeSource TemplatedParent}}" />
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>
                            <Storyboard x:Key="DisabledStoryboard">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                              Storyboard.TargetProperty="Background.Color">
                                    <EasingColorKeyFrame KeyTime="0" Value="{Binding Path=(theming:DisabledProperties.BackgroundColor), RelativeSource={RelativeSource TemplatedParent}}" />
                                </ColorAnimationUsingKeyFrames>
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                              Storyboard.TargetProperty="BorderBrush.Color">
                                    <EasingColorKeyFrame KeyTime="0" Value="{Binding Path=(theming:DisabledProperties.BorderColor), RelativeSource={RelativeSource TemplatedParent}}" />
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>
                        </Grid.Resources>

                        <!-- ... -->
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="{StaticResource ColorAnimationDuration}" />
                                    <VisualTransition To="Disabled" GeneratedDuration="0" />
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="MouseOver" Storyboard="{StaticResource MouseOverStoryboard}" />
                                <VisualState x:Name="Pressed" Storyboard="{StaticResource PressedStoryboard}" />
                                <VisualState x:Name="Disabled" Storyboard="{StaticResource DisabledStoryboard}" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>

这篇关于有没有办法更改在 Base-Style 中定义的 ControlTemplate 的特定属性,而无需复制粘贴整个代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆