本文介绍了在本地/直接设置控件的(触发)属性时,ControlTemplate/样式触发器不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在聚焦或悬停控件时更改 BorderBrush .
效果很好,除非在窗口中设置默认的 BorderBrush .
在那种情况下,即使我聚焦或悬停控件, BorderBrush 也不会更改.

I want to change BorderBrush when I focus or hover the control.
It works great, except when in window I set the default BorderBrush.
In that case the BorderBrush is not changed anymore even if I focus or hover the control.

我已经有一个解决方案:创建另一个属性以避免直接更改主要的默认属性,并将主要属性绑定到该属性以获取默认值.
但我想知道是否还有另一种解决方案,而不添加无用的属性,并且几乎没有复制&;将整个模板粘贴到每个触发器上.

I have already a solution: create another property to avoid directly changing the main default property, and bind the main property to that, for default value.
But I want to know if there is another solution without adding an useless property, and without almost copy & paste the entire template on every trigger.

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

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

    <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">
                        <ScrollViewer x:Name="PART_ContentHost"
                                      Margin="0" />
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrush" Value="RoyalBlue"/>
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="BorderBrush" Value="SteelBlue"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="BorderBrush" Value="Gray"/>
        </Trigger>
    </Style.Triggers>

</Style>

我的 BorderBrushValue 解决方案:

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

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

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

    <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 BorderBrushValue}"
                                Background="{TemplateBinding Background}"
                                SnapsToDevicePixels="True">
                        <ScrollViewer x:Name="PART_ContentHost"
                                      Margin="0" />
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrushValue" Value="RoyalBlue"/>
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="BorderBrushValue" Value="SteelBlue"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="BorderBrushValue" Value="Gray"/>
        </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">

        <!-- this one, with first style, not works -->
        <local:IconTextBox HorizontalAlignment="Left" VerticalAlignment="Top"
                           Width="200" Height="25" Margin="80,80,0,0"
                           BorderBrush="#FF1E1E1E"/>

    </Grid>
</Window>

推荐答案

在本地设置属性(即直接设置)将始终覆盖此属性的 Style 设置.由于触发器绑定到属性,因此它们也将被覆盖.请参阅 Microsoft文档:依赖项属性设置优先级列表以获取更多信息.

Setting a property locally i.e. directly will always override the Style setting of this property. Since triggers are bound to properties they are overridden too. See Microsoft Docs: Dependency Property Setting Precedence List for more info.

这通常不是问题,因为您定义隐式的 Style 旨在创建默认主题.UI设计的一般规则是保持外观一致.

This usually isn't an issue as you define an implicit Style with the intention to create a default theme. And a general rule of UI design is to keep the look consistent.

如前所述, Trigger DataTrigger 是属性绑定的或基于属性的状态的.当解析器尝试解析属性值(在本例中为触发器操作)时,将解决触发器.

As mentioned before, Trigger an DataTrigger are property bound or based on the property's state. The trigger is resolved when the parser tries to resolve the property value (which will be in this case a trigger action).

由于 DependencyProperty 值的优先级,您应该定义专门的 Style 来覆盖默认值.本地值将使XAML解析器停止查找该属性的任何 Style 设置,因此将忽略所有特定于属性的触发器.

Because of the DependencyProperty value precedence, you should define a specialized Style to override the default. Local values will stop the XAML parser to lookup for any Style setting of the property and therefore ignore all property specific triggers.

专门的 Style 应该基于默认设置,以允许选择性覆盖:

The specialized Style should be based on the default to allow selective overrides:

<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="RoyalBlue" />
              </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="Blue" />
    </Style>
  </Window.Resources>

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

解决方案2:VisualStateManager(推荐)

如果您希望处理视觉效果而不必定义特殊样式,则应使用 VisualStateManager .由于这种触发是基于事件的(与基于属性状态的 Trigger DataTrigger 相对),因此在本地设置属性值不会覆盖触发器.

Solution 2: VisualStateManager (recommended)

If you prefer to handle visual effects without having to define specialzed styles, you should use the VisualStateManager. Since this kind of triggering is event based (opposed to the property state based Trigger and DataTrigger), setting property values locally won't override the trigger.

这就是为什么您可以在默认情况下在每个控件上设置诸如 Background 之类的属性而不会破坏视觉效果的原因-默认情况下,控件是使用 VisualStateManager 来处理视觉状态的.

This is why you can set properties like Background on every control by default without breaking the visual effects - by default controls are implemented using the VisualStateManager to handle visual states.

可选:当您想要保持外观灵活(主题)时,可以使用 ComponentResourceKey .定义 ResourceKey 可以通过使用相同的 x:Key 定义新资源来替换主题资源.

Optional: when you want to keep the appearance flexible (theming) you can make use of the ComponentResourceKey. Defining a ResourceKey allows to replace a theme resource by defining a new resource using the same x:Key.

VisualStateManager 使控件的使用及其自定义更加方便:

VisualStateManager makes the usage of controls and their customization more convenient:

IconTextBox.cs (可选)

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

Generic.xaml

<ResourceDictionary>

  <!-- Define the original resource (optional)-->
  <Color x:Key="{ComponentResourceKey {x:Type IconTextBox}, BorderBrushOnMouseOver}">RoyalBlue</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.BorderBrushOnMouseOverKey}}" />
                    </ColorAnimationUsingKeyFrames>
                  </Storyboard>
                </VisualState>
              </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

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

App.xaml (可选)覆盖默认颜色资源:

App.xaml (optional)Override the default color resource:

<ResourceDictionary>

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

访问 Microsoft Docs:控件样式和模板,以查找所请求控件的默认样式.您将找到所有必需的模板部分以及特定控件定义的所有可用视觉状态.您可以使用此信息来实现 VisualStateManager .例如. Microsoft Docs:文本框状态

Visit Microsoft Docs: Control Styles and Templates to find the default style of the requested control. You will find all required template parts as well as all available visual states that are defined by the specific control. You can use this information to implement the VisualStateManager. E.g. Microsoft Docs: TextBox States

这篇关于在本地/直接设置控件的(触发)属性时,ControlTemplate/样式触发器不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-06 00:48