【问题标题】:ControlTemplate/Style triggers are not working, when setting control's (triggering) properties locally/directly在本地/直接设置控件的(触发)属性时,ControlTemplate/Style 触发器不起作用
【发布时间】:2020-10-17 07:40:03
【问题描述】:

当我聚焦或悬停控件时,我想更改 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="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>

【问题讨论】:

    标签: wpf triggers binding overriding custom-controls


    【解决方案1】:

    在本地(即直接)设置属性将始终覆盖此属性的Style 设置。由于触发器绑定到属性,因此它们也被覆盖。请参阅Microsoft Docs: Dependency Property Setting Precedence List 了解更多信息。

    这通常不是问题,因为您定义了一个隐含的Style 以创建默认主题。 UI 设计的一般规则是保持外观一致。

    解决方案 1:触发器(和 DataTrigger) - 不推荐

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

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

    专门的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="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。由于这种触发是基于事件的(相对于基于属性状态的TriggerDataTrigger),在本地设置属性值不会覆盖触发器。

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

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

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

    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(可选) 覆盖默认颜色资源:

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

    访问Microsoft Docs: Control Styles and Templates 以查找所请求控件的默认样式。您将找到所有必需的模板部件以及由特定控件定义的所有可用视觉状态。您可以使用此信息来实现VisualStateManager。例如。 Microsoft Docs: TextBox States

    【讨论】:

      【解决方案2】:

      也许我误解了这个问题,但你应该能够在样式上设置一个键。如果它是用一个键设置的,它不会覆盖默认值,因为您必须专门将样式应用于控件。

      设置你的风格的关键

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

      在需要的UserControl上设置特定样式

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

      【讨论】:

        【解决方案3】:

        我发现除了solutions of @BionicCode之外的第三种解决方案,在对触发器有更多了解后,并考虑到我的上下文,不是更改样式的本地属性,而是直接更改控件模板的属性元素,使用 TargetName 定位它们。

        所以这是我的第三个解决方案:

        <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 x:Name="BorderElement"
                                    Margin="{TemplateBinding InputMargin}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    Background="{TemplateBinding Background}"
                                    SnapsToDevicePixels="True">
                                <ScrollViewer x:Name="PART_ContentHost"
                                              Margin="0" />
                            </Border>
                        </Grid>
        
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="BorderElement" Property="BorderBrush"
                                        Value="RoyalBlue"/>
                            </Trigger>
                            <Trigger Property="IsFocused" Value="True">
                                <Setter TargetName="BorderElement" Property="BorderBrush"
                                        Value="SteelBlue"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter TargetName="BorderElement" Property="BorderBrush"
                                        Value="Gray"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
        
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        

        注意触发器必须是控件模板的一部分,否则不能引用BorderElement(你会得到一个编译错误,因为你不能在样式中使用TargetName!)。

        所以,现在可以正常工作了:

        <local:IconTextBox HorizontalAlignment="Left" VerticalAlignment="Top"
                           Width="200" Height="25" Margin="80,80,0,0"
                           BorderBrush="#FF1E1E1E"/>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-02-27
          • 2019-12-21
          • 2010-12-17
          • 2013-08-30
          • 2018-09-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多