Trigger set in Style not working as expected - wpf

I created a class library containing a style for almost every user control. In my applications i add this style library as a nuget package.
<Style TargetType="{x:Type Button}" x:Key="tkButton">
<Setter Property="Background" Value="{StaticResource exQuiteDarkBrush}"/>
<Setter Property="Foreground" Value="{StaticResource tkLightGrayBrush}"/>
<Setter Property="FontFamily" Value="{StaticResource TKTypeRegular}"/>
<Setter Property="BorderBrush" Value="{StaticResource tkMediumGrayBrush}"/>
<Setter Property="MinHeight" Value="25"/>
<Setter Property="MinWidth" Value="60"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource tkBrandBlueBrush}"/>
<Setter Property="Foreground" Value="{StaticResource tkBrandBlueBrush}"/>
</Trigger>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter Property="Foreground" Value="{StaticResource tkMediumGrayBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource tkDarkGrayBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
The library contains for example a simple button. In most Usercontrols i use a trigger
for the IsMouseOver Property to change some colors.
In my Application I'm using the style like this:
<Button Style="{DynamicResource tkButton}" Content="button with ITALIC and BOLD type" FontStyle="Italic" FontWeight="Bold" IsEnabled="{Binding Enabled}"/>
<Button BorderBrush="{StaticResource tkRedBrush}" Style="{DynamicResource tkButton}" Content="button with ITALIC type" FontStyle="Italic" IsEnabled="{Binding Enabled}"/>
The first button adds the style and works as expected when hovering over it.
The second one uses the style, but changes the BorderBrush to red. When hovering over the second Button i expect to change the BorderBrush but it stays red.
The only solution i can imagine is to set the trigger inside the window/application again.
Are there any different solutions to change this behavior?

Move the trigger to the ControlTemplate:
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="{StaticResource tkBrandBlueBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
If you put it in the Style, the local value specified by BorderBrush="{StaticResource tkRedBrush}" will take precedence over the value set by Setter: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-property-value-precedence

You can try to declare your button on the following way, by inheriting tkButton style and overriding the BorderBrush value, because BorderBrush="{StaticResource tkRedBrush}" has higher precedence than style declaration
<Button Content="button with ITALIC type" FontStyle="Italic" IsEnabled="{Binding Enabled}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource tkButton}">
<Setter Property="BorderBrush" Value="{StaticResource tkRedBrush}"/>
</Style>
</Button.Style>
</Button>

Related

Can't pass CornerRadius through TemplateBinding/TemplatedParent

I have a style for Buttons and now I want to apply it to RadioButtons, except that where a normal Button has a CornerRadius that is a single number (each corner is the same) I want to render the RadioButtons with different corner radii, like this:
I use a custom ControlTemplate for Button as well as a style:
<ControlTemplate TargetType="{x:Type ButtonBase}" x:Key="ButtonTemplate">
<Border Background="{TemplateBinding Background}"
Width="{TemplateBinding Width}"
CornerRadius="22.5"
BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding Foreground}">
<TextBlock FontSize="14" FontWeight="Bold"
TextWrapping="Wrap" TextAlignment="Center"
VerticalAlignment="Center" HorizontalAlignment="Center" Margin="30 0"
Foreground="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}"
Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"/>
</Border>
</ControlTemplate>
<Style TargetType="{x:Type ButtonBase}" x:Key="MainButton">
<Setter Property="Foreground" Value="{StaticResource IntBlue2}"/>
<Setter Property="TextBlock.Foreground" Value="{StaticResource IntBlue2}"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="MinWidth" Value="132"/>
<Setter Property="Template" Value="{StaticResource ButtonTemplate}"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="LightSlateGray"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="#66CDFF"/>
</Trigger>
</Style.Triggers>
</Style>
And I have a style for RadioButton that uses the Button template
<Style TargetType="RadioButton">
<Setter Property="Template" Value="{StaticResource ButtonTemplate}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource ForegroundPureWhite}"/>
<Setter Property="BorderThickness" Value="2"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Foreground" Value="{StaticResource DisabledGrey}"/>
</Trigger>
</Style.Triggers>
</Style>
Everything above works, until I want the custom CornerRadius for each RadioButton.
I've tried CornerRadius="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}}" inside the Border in the ControlTemplate, and <Setter Property="Border.CornerRadius" Value="22.5"/> inside the MainButton style, but it doesn't work (it doesn't apply the radius).
Binding CornerRadius="{TemplateBinding CornerRadius}" to the border in the template doesn't even compile, and setting CornerRadius on a Button or RadioButton, through the control tag or through a setter in the style, also doesn't compile.
How do I use one template, but different CornerRadius in each style or instance?
A ButtonBase has no CornerRadius property that you can bind to but you could use the Tag property:
<ControlTemplate TargetType="{x:Type ButtonBase}" x:Key="ButtonTemplate">
<Border Background="{TemplateBinding Background}"
Width="{TemplateBinding Width}"
CornerRadius="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"
...
<Style TargetType="RadioButton">
<Setter Property="Tag" Value="22.5" />
...
</Style>
<RadioButton ... Tag="0" />

How to change border color using triggers in WPF?

I need to change border color of one text box when mouse hover him, but with solution i write it does not work. Is there mistake in my code?
Background color changes, but border no.
Code below is my idea.
<Style x:Key="BorderColor" TargetType="TextBox">
<Setter Property="FontStyle" Value="Normal"/>
<Style.Triggers>
<Trigger Property="TextBox.IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Background" Value="LightBlue"/>
</Trigger>
</Style.Triggers>
A quick and easy way to do this is to simply set the TextBox border thickness to 0 and encapsulate it in a parent border:
<Border BorderThickness="1" Width="500" Height="20" >
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="Black" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBox x:Name="theTextBox" BorderThickness="0" />
</Border>
The "proper" way to do this is to template the entire control:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" TargetName="border" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Color of button border is controlled by template triggers of a button via button (Templated Parent) properties. Since priority of such triggers is almost the top most, you need to redefine control template of a button.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-property-value-precedence

Setting border size on button on mouse over in WPF

I am trying to set the border colour property on a button when user hovers on it. Currently I am using following XAML.
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#FF0081a7"/>
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="BorderThickness" Value="2,2,2,2"/>
<Setter Property="Foreground" Value="#FFffffff"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#ccCCCCCC"/>
<Setter Property="BorderBrush" Value="Gold"/>
<Setter Property="BorderThickness" Value="2,2,2,2"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
This works fine only for colour changes. Border on the other hand is not displayed at all. I am sure, I am missing something very simple, only thing is I can't find what it is.
You're not actually utilising the BorderBrush and BorderThickness properties... you need to actually do something with them from inside your ControlTemplate. Try this:
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding
BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>

WPF Trigger Not Working As Intended

I want a red button that turns black when the mouse hovers over it.
<Button Content="Hover me" Grid.Column="3" Grid.Row="3">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Black"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
However, my problem is that when I hover over the button, it turns into the default Windows style with a gradient gray appearance.
Try it
<Window.Resources>
<Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter x:Name="PART_Content"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
TextElement.Foreground="{TemplateBinding Foreground}"></ContentPresenter> </Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
and apply the custom Style as following
<Button Content="Hover me" Style="{StaticResource MyButtonStyle}" Height="30" Width="100"/>
The reason is the default Aero style of a Button. It has a chrome defined in ControlTemplate, which has it own behavior on various mouse events. So That over write your trigger call.
So you must override default ControlTemplate of Button to achieve your desired result.

Trying to inherit theme / style and applying additional triggers

I'm trying to work with, and understand XAML hierarchy for styles... in simple, a simple Textbox... seen all over the place for how to set the "disabled" background color based on the "IsEnabled" flag. Great, got that.
Now, I want to have another class derived from TextBox... MyTextBox. For this class, I have a property (not dependency property, so I was using DataTrigger). So, I want to keep all the normal TextBox actions that were working, but now get the new trigger to properly update the background color to some other color.. So, here is what I have. just to clarify, all my static resources for colors are SOLID BRUSHES...
<Style TargetType="TextBox" >
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource MyForeground}" />
<Setter Property="Background" Value="{StaticResource MyBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Name="Bd" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ScrollViewer Name="PART_ContentHost"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource MyDisBackground}" />
<Setter TargetName="PART_ContentHost" Property="Background"
Value="MyDisBackground"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Now, my derived (or so I was hoping) style that just adds additional trigger -->
<Style TargetType="local:MyTextBox" BasedOn="{StaticResource {x:Type TextBox}}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsRequired}" Value="True">
<Setter Property="Background" Value="{StaticResource RequiredBackground}" />
</DataTrigger>
</Style.Triggers>
</Style>
Am I missing something simple?
First, your DataTrigger is looking at the DataContext of your MyTextBox (not the control itself). So look at the control, you'd need to do something like:
<DataTrigger Binding="{Binding Path=IsRequired, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="{StaticResource RequiredBackground}" />
</DataTrigger>
Now that will set the MyTextBox.Background property when MyTextBox.IsRequired is true. But dependency property values have a precedence order. So the above will visually change the background used like:
<local:MyTextBox />
In the following case your RequiredBackground brush will not be used. Instead you'll see the MyDisBackground brush:
<local:MyTextBox IsEnabled="False" />
In this case, the ScrollViewer.Background is changed to MyDisBackground and no longer binds to the MyTextBox.Background property. The MyTextBox.Background would still be RequiredBackground, but it's no longer used anywhere.
Finally, in the following case your RequiredBackground brush will not be used.
<local:MyTextBox Background="Yellow" />
Here, the local value (yellow) is at #3 in the precedence list, while the style setter is at #8.
If you make your property a dependency property that defaults to false, then you could do something like:
<Style TargetType="TextBox" >
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource MyForeground}" />
<Setter Property="Background" Value="{StaticResource MyBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Name="Bd" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ScrollViewer Name="PART_ContentHost"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="local:MyTextBox.IsRequired" Value="False">
<Setter Property="Background" Value="{StaticResource RequiredBackground}" />
<Setter TargetName="PART_ContentHost" Property="Background"
Value="RequiredBackground"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource MyDisBackground}" />
<Setter TargetName="PART_ContentHost" Property="Background"
Value="MyDisBackground"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:MyTextBox" BasedOn="{StaticResource {x:Type TextBox}}" />
Eventhough the property doesn't exist for TextBox, it can still get the default value of your dependency property and trigger off it. But since it would be set for a TextBox, that trigger will never be used.

Resources