首页 > 解决方案 > Workaround for dependency property value precendence

问题描述

I'm builiding a custom control as seen below:

<UserControl x:Class="App.Views.Components.MenuButton"
    [...]>
    <UserControl.Resources>
        <Style TargetType="local:MenuButton">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Foreground" Value="{Binding HoverForeground, RelativeSource={RelativeSource Self}}"/>
                    <Setter Property="Background" Value="{Binding HoverBackground, RelativeSource={RelativeSource Self}}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
</UserControl>

The said control has 2 dependency properties: HoverForeground and HoverBackground, why did I define them? - So I can have infinite amount of buttons with relatively easy to set hover color.

The issue is, I can't walk around DPVP and whenever I set Foreground in another control (as seen below), the foreground will no longer change upon IsMouseOver event.

Here's the code for another control:

<UserControl x:Class="App.Views.Components.Menu"
    [...]>
    <StackPanel Orientation="Vertical">
        <local:MenuButton Foreground="{DynamicResource BackgroundDark}" HoverForeground="Red" HoverBackground="Red" Margin="8" Content="" FontSize="24"/>
        <local:MenuButton Foreground="{DynamicResource BackgroundDark}" HoverForeground="Green" HoverBackground="Green" Margin="8" Content="" FontSize="24"/>
        <local:MenuButton Foreground="{DynamicResource BackgroundDark}" HoverForeground="Blue" HoverBackground="Blue" Margin="8" Content="" FontSize="24"/>
        <local:MenuButton Foreground="{DynamicResource BackgroundDark}" HoverForeground="Black" HoverBackground="Yellow" Margin="8" Content="" FontSize="24"/>
    </StackPanel>
</UserControl>

I appreciate any input.

标签: wpf

解决方案


我阅读了您的问题,如果我理解正确,Foreground在您的控件上设置 会覆盖您通过控件的Style触发器对同一属性所做的更改。

我不建议您使用更改控件属性的触发器,特别是常见的属性,如BackgroundForeground。您的控件的属性旨在由您的控件的使用者从您的代码外部设置。如果您Style在其上使用并使用触发器来更改控件上的属性以响应某些事件,那么这对于您的控件的使用者来说可能是意外的,并且当使用者显式设置这些相同的属性时会被覆盖。您应该在控件上动态更改的唯一属性是只读属性。

在控件的生命周期内动态更改控件外观的更好方法(也是预期的方法)是使用ControlTemplate. 控件模板不会更改控件的属性;相反,它们会更改自己元素的属性,这些元素用于绘制控件的视觉效果。例如,您可以使用 a Triggeron yourControlTemplate来在鼠标悬停在控件上时更改某些视觉元素的画笔,其方式与您在Style's 触发器上所做的操作类似。除了您之外,没有人可以访问您正在更改画笔的元素,因此您可以随时随意更改您想要的内容。

此示例应该让您了解如何创建为您的控件Style设置 a 的 a 。ControlTemplate我只是匆匆写了代码,没有测试它,所以它可能是一些错字或其他东西:

<Style TargetType="{x:Type local:MenuButton">
  <Setter Property="Background" Value="#282828"/>
  <Setter Property="Foreground" Value="#D0D0D0"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:MenuButton}">
        <Border x:Name="TemplateRoot"
                Background="{TemplateBinding HoverBackground}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}">
          <ContentPresenter x:Name="contentPresenter" 
                            Content="{TemplateBinding Content}"
                            Margin="{TemplateBinding Padding}"
                            HorizontalAlignement="{TemplateBinding HorizontalContentAlignement}"
                            VerticalAlignement="{TemplateBinding VerticalContentAlignement}"/>
        </Border>
        <ControlTemplate.Triggers>
          <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="TemplateRoot" Property="Background" 
                    Value="{Binding HoverBackground, 
                            RelativeSource={RelativeSource TemplatedParent}}"/>
            <Setter TargetName="contentPresenter" Property="TextElement.Foreground" 
                    Value="{Binding HoverForeground, 
                            RelativeSource={RelativeSource TemplatedParent}}"/>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

需要注意的一些事情ControlTemplate是,它主要使用TemplateBinding扩展来绑定到模板化控件的属性,而不是通常的Binding,尽管最后一个也可以。使用 时Binding,您指的是使用带有模式的RelativeSource源的模板化控件。TemplatedParent例如:

{Binding Path=Background, RelativeSource={RelativeSource Mode=TemplatedParent}}

但是TemplateBinding在可以使用的地方使用 ,只是更方便。所以上面的绑定可以这样写:

{TemplateBinding Background}

为控件设置自定义可理解的不利方面ControlTemplate是我们需要从头开始编写它,这有时会很痛苦。对于复杂的控件尤其如此。当我们只需要更改视觉上的一些东西时,直接在控件上进行更改似乎很诱人Style,就像您正在做的那样。但是,我建议不要这样做,因为它往往会产生比它解决的问题更多的问题。


Style并且ControlTemplate有一些具有误导性的名称。基本上,它们都旨在以可重用的方式在您的应用程序中对控件进行操作。简而言之,当您想要设置与控件外观相关的内容并且可以重用时,您应该使用ControlTemplate.

Style旨在用于设置控件属性的默认值。a 的触发器Style也不例外;它们旨在为控件的属性设置默认值,但它们能够根据某些条件进行设置。例如,aTabControl可能需要在其上使用触发器Style来根据 的值设置其Template属性的值TabStripPlacement,因此控件会根据选项卡是放在左侧、顶部、右侧还是底部来自动切换模板。


推荐阅读