Триггеры ControlTemplate / Style не работают при локальной (прямой) настройке свойств элемента управления - PullRequest
0 голосов
/ 16 апреля 2020

Я хочу изменить BorderBrush при фокусировке или наведении на элемент управления.
Он отлично работает, кроме случаев, когда в окне я установил значение по умолчанию BorderBrush.
В этом случае BorderBrush не изменяется больше, даже если я сфокусируюсь или наведите курсор на элемент управления.

У меня уже есть решение: создайте другое свойство, чтобы избежать прямого изменения основного свойства по умолчанию, и привяжите к нему основное свойство для значения по умолчанию.
Но я хочу знать, есть ли другое решение без добавление бесполезного свойства и практически без копирования и вставки всего шаблона в каждый триггер.

<Style TargetType="{x:Type local:IconTextBox}" BasedOn="{StaticResource {x:Type TextBox}}">

    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="BorderBrushOnMouseHover" Value="RoyalBlue"/>
    <Setter Property="BorderBrushOnFocused" Value="SteelBlue"/>
    <Setter Property="BorderBrushOnDisabled" Value="Gray"/>
    <!-- other setter -->

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:IconTextBox}">
                <Grid>
                    <Image Source="{TemplateBinding Icon}" 
                            HorizontalAlignment="Left"
                            SnapsToDevicePixels="True"/>
                    <Border Margin="{TemplateBinding InputMargin}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                Background="{TemplateBinding Background}"
                                SnapsToDevicePixels="True">
                        <Grid>
                            <!-- content removed for brevity -->
                        </Grid>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnMouseHover}"/>
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnFocused}"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnDisabled}"/>
        </Trigger>
    </Style.Triggers>

</Style>

Мое решение с BorderBrushDefault:

<Style TargetType="{x:Type local:IconTextBox}" BasedOn="{StaticResource {x:Type TextBox}}">

    <Setter Property="BorderThickness" Value="1"/>

    <!-- FIX -->
    <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushDefault}"/>
    <Setter Property="BorderBrushDefault" Value="Black"/>
    <!-- FIX -->

    <Setter Property="BorderBrushOnMouseHover" Value="RoyalBlue"/>
    <Setter Property="BorderBrushOnFocused" Value="SteelBlue"/>
    <Setter Property="BorderBrushOnDisabled" Value="Gray"/>
    <!-- other setter -->

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:IconTextBox}">
                <Grid>
                    <Image Source="{TemplateBinding Icon}" 
                            HorizontalAlignment="Left"
                            SnapsToDevicePixels="True"/>
                    <Border Margin="{TemplateBinding InputMargin}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                Background="{TemplateBinding Background}"
                                SnapsToDevicePixels="True">
                        <Grid>
                            <!-- content removed for brevity -->
                        </Grid>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnMouseHover}"/>
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnFocused}"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnDisabled}"/>
        </Trigger>
    </Style.Triggers>

</Style>

Окно:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp"
        x:Class="WpfApp.Home"
        mc:Ignorable="d"
        Title="Home" Height="450" Width="800">
    <Grid Background="#FF272727">
        <local:IconTextBox HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="25" Margin="80,88,0,0"
                        BorderBrushDefault="#FF1E1E1E"
                        BorderBrushOnDisabled="#FF470303" BorderBrushOnFocused="#FF1176BB" BorderBrushOnMouseHover="#FF1176BB"/>

        <!-- this one not works -->
        <local:IconTextBox HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="25" Margin="80,88,0,0"
                        BorderBrush="#FF1E1E1E"
                        BorderBrushOnDisabled="#FF470303" BorderBrushOnFocused="#FF1176BB" BorderBrushOnMouseHover="#FF1176BB"/>
    </Grid>
</Window>

Ответы [ 2 ]

0 голосов
/ 17 апреля 2020

Установка свойства локально, то есть напрямую, всегда будет переопределять настройку Style этого свойства. Поскольку триггеры связаны со свойствами, они также переопределяются. См. Документы Microsoft: список приоритетов настройки свойств зависимостей для получения дополнительной информации.

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

Решение 1. Триггер (и DataTrigger) - не рекомендуется

Как упоминалось ранее, Trigger и DataTrigger привязаны к свойству или основаны на его состоянии. Триггер разрешается, когда синтаксический анализатор пытается разрешить значение свойства (которое в данном случае будет действием триггера).

Из-за приоритета DependencyProperty вы должны определить специализированный Style для переопределения по умолчанию. Локальные значения остановят синтаксический анализатор XAML для поиска любого параметра Style свойства и, следовательно, проигнорируют все триггеры, указанные для свойства c.

Специализированная Style должна основываться на значениях по умолчанию, чтобы разрешить выборочные переопределения:

<Window>
  <Window.Resources>

    <!-- Implicit default Style -->
    <Style TargetType="TextBox">
      <Setter Property="BorderBrush" Value="Black" />

      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="TextBox">
            <Border BorderThickness="{TemplateBinding BorderThickness}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    Background="{TemplateBinding Background}">
              <ScrollViewer x:Name="PART_ContentHost"
                            Margin="0" />
            </Border>

            <ControlTemplate.Triggers>
              <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="BorderBrush" 
                        Value="{Binding RelativeSource={RelativeSource Self}, Path=BorderBrushOnMouseHover}" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <!-- Explicit specialized Style based on the implicit default Style -->
    <Style x:Key="SpecializedStyle" 
           TargetType="TextBox"
           BasedOn="{StaticResource {x:Type TextBox}}">
      <Setter Property="BorderBrush" Value="#FF1E1E1E" />

      <!-- Other default overrides -->
      <Setter Property="BorderBrushOnDisabled" Value="#FF470303" />
      <Setter Property="BorderBrushOnFocused" Value="#FF1176BB" />
      <Setter Property="BorderBrushOnMouseHover" Value="#FF1176BB" />
    </Style>
  </Window.Resources>

  <!-- This now works -->
  <local:IconTextBox Style="{StaticResource SpecializedStyle}" />
</Window>

Решение 2: VisualStateManager (рекомендуется)

Если вы предпочитаете обрабатывать визуальные эффекты без необходимости определения специальных стилей, вы должны использовать VisualStateManager. Так как этот тип запуска основан на событиях (в отличие от состояний Trigger и DataTrigger), локальные установки значений свойств не будут переопределять триггер.

Именно поэтому вы можете установить свойства, такие как Background, для каждого элемента управления по умолчанию, не нарушая визуальные эффекты - по умолчанию элементы управления реализованы с использованием VisualStateManager для обработки визуальных состояний.

Необязательно : если вы хотите сохранить гибкость внешнего вида (темы), вы можете использовать ComponentResourceKey. Определение ResourceKey позволяет заменить ресурс темы путем определения нового ресурса с использованием того же x:Key.

VisualStateManager делает использование элементов управления и их настройку более удобной:

IconTextBox.cs (необязательно)

class IconTextBox : TextBox
{
  public static ComponentResourceKey DefaultBorderBrushKey 
    => new ComponentResourceKey(typeof(IconTextBox), "DefaultBorderBrush");
}

Generi c .xaml

<ResourceDictionary>

  <!-- Define the original resource (optional)-->
  <Color x:Key="{ComponentResourceKey {x:Type IconTextBox}, DefaultBorderBrush}">OrangeRed</Color>

  <Style TargetType="IconTextBox">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="IconTextBox">
          <Border x:Name="Border"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  Background="{TemplateBinding Background}">
            <VisualStateManager.VisualStateGroups>
              <VisualStateGroup x:Name="CommonStates">
                <VisualStateGroup.Transitions>
                  <VisualTransition GeneratedDuration="0:0:0.5" />
                </VisualStateGroup.Transitions>
                <VisualState x:Name="Normal" />
                <VisualState x:Name="MouseOver">
                  <Storyboard>
                    <ColorAnimationUsingKeyFrames
                      Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
                      Storyboard.TargetName="Border">
                      <EasingColorKeyFrame KeyTime="0"
                                           Value="{DynamicResource {x:Static IconTextBox.DefaultBorderBrushKey}}" />
                    </ColorAnimationUsingKeyFrames>
                  </Storyboard>
                </VisualState>
              </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <ScrollViewer x:Name="PART_ContentHost" 
                          Margin="0" />
          </Border>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

App.xaml (необязательно) Переопределите ресурс цвета по умолчанию:

<ResourceDictionary>

  <!-- Override the original resource -->
  <Color x:Key="{x:Static IconTextBox.DefaultBorderBrushKey}" >Blue</Color>
</ResourceDictionary>

Посетите Документы Microsoft: стили и шаблоны элементов управления , чтобы найти стиль по умолчанию для запрошенного элемента управления. Вы найдете все необходимые части шаблона, а также все доступные визуальные состояния, которые определены с помощью указанного элемента управления c. Вы можете использовать эту информацию для реализации VisualStateManager. Например, Документы Microsoft: состояния TextBox

0 голосов
/ 16 апреля 2020

Может быть, я неправильно понимаю проблему, но вы должны просто установить ключ к стилю. Если для него задана клавиша, он не будет переопределять значение по умолчанию, поскольку вам необходимо специально применить стиль к элементу управления.

Установить ключ для вашего стиля

<Style x:Key="myStyle" ...

Задать спецификацию c стиль на желаемом UserControl что нужно

<local:IconTextBox Style="{StaticResource myStyle}"...
...