依赖项属性值优先级的解决方法



我正在构建一个自定义控件,如下所示:

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

所述控件具有 2 个依赖项属性:悬停前景和悬停背景,我为什么要定义它们? - 所以我可以有无限数量的按钮,相对容易设置悬停颜色。

问题是,我无法在DPVP周围走动,每当我在另一个控件中设置前景时(如下所示),前景将不再在IsMouseOver事件时更改。

下面是另一个控件的代码:

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

我感谢任何意见。

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

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

在控件的生存期内动态更改控件外观的更好方法(也是预期方法)是使用ControlTemplate。控件模板不会更改控件的属性;相反,它们更改自己的元素的属性,这些元素用于绘制控件的视觉对象。例如,您可以在ControlTemplate上使用一个Trigger,当鼠标悬停在控件上时,它会更改某些视觉元素的画笔,其方式类似于您在Style触发器上所做的那样。除了你之外,没有人可以访问你正在更改画笔的元素,所以你可以随时自由地改变你想要的东西。

此示例应让您了解如何创建为控件设置ControlTemplateStyle。我只是匆忙编写了代码,没有对其进行测试,所以可能是一些错别字或其他东西:

<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时,可以使用具有TemplatedParent模式的RelativeSource源引用模板化控件。例如:

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

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

{TemplateBinding Background}

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


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

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

最新更新