Есть ли способ изменить конкретные свойства ControlTemplate, определенные в базовом стиле, без необходимости копировать и вставлять весь код? - PullRequest
0 голосов
/ 01 июля 2018

Я хочу создать стиль для двух кнопок, причем обе они будут иметь одинаковые свойства, за исключением их цветов. См. Это изображение для справки.

В идеале я хотел бы создать базовый стиль с общим ControlTemplate для этих кнопок один раз, а затем каким-то образом переключить цвета.

Первоначально я думал создать базовый стиль, который использует ранее определенные SolidColorBrush ресурсы для цветов, а затем добавить два дополнительных стиля (Accent1Button-Style и Accent2Button-Style), которые перезаписывают эти SolidColorBrush ресурсы. Код будет выглядеть так:

<!-- 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 не перезаписывает ресурсы.

Другим подходом было создание вспомогательного класса, который предлагает присоединенное свойство (например, ColorHelper.HoverColor). Я думал об использовании этого свойства в шаблоне и настройке его в стилях Accent1 / Accent2, но это невозможно, поскольку для этого потребуются привязки TemplateBindings, которые не поддерживаются ColorAnimation, используемым в шаблоне.

Прямо сейчас, единственный вариант, который я вижу, - это скопировать / вставить весь шаблон и переключить цвета в соответствующих местах (например, внутри анимации). Однако я бы хотел этого избежать.

Итак, наконец, есть способ определить мой базовый стиль только один раз, одновременно создавая отдельные «подстили», которые изменяют специфичные для шаблона свойства, которые не являются частью класса Button (например, HoverColor, PressedColor, ...)?

Edit: Чтобы выяснить, почему подход с вложенными свойствами не будет работать: предположим, что я ввел класс ColorHelper со следующим вложенным свойством:

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);
}

Теоретически я мог бы установить это свойство в своих подстилях так:

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

Проблема этого решения заключается в базовом стиле, а точнее в следующей части кода:

<!-- 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 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')

1 Ответ

0 голосов
/ 03 июля 2018

После трех дней поиска решения я наткнулся на следующий пост: Привязка к свойству элемента управления внутри VisualStateManager

Я мог бы решить мою проблему, используя точно такой же подход с BindingProxy (мне нужно было только переключиться с DoubleAnimation на ColorAnimation).

Edit: Через некоторое время я обнаружил лучший способ. Пока работает решение BindingProxy, гораздо проще просто поместить экземпляры Storyboard в ресурсы элемента, который появляется в визуальном дереве. Сделав это, Bindings сможет найти 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...