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.
Related
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" />
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>
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
XAML:
<RadioButton Margin="15" Grid.Row="0" Grid.Column="3" Style=" {StaticResource SpeedButtonStyle}" Content="TEST"/>
Style:
<!-- Speed Button Style -->
<Style x:Key="SpeedButtonStyle" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="FontSize" Value="18px"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Background" Value="{StaticResource SidePanelButtonBgInactive}"/>
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource SidePanelButtonBgActive}"/>
<Setter Property="Foreground" Value="{StaticResource SidePanelButtonFgActive}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="BorderThickness" Value="30"/>
<Setter Property="BorderBrush" Value="White" />
<Setter Property="Background" Value="{StaticResource SidePanelButtonBgActive}"/>
<Setter Property="Foreground" Value="{StaticResource SidePanelButtonFgActive}"/>
</Trigger>
</Style.Triggers>
</Style>
I am trying to change the size of the border on my radio button which is styled like a toggle button. I can change the colour of the border but not the size. it seems to be using the default size which is realy thin.
I'm using my templated ToggleRadioButton and you can achieve it by binding BorderThickness to its templated parent.
<Style TargetType="RadioButton" x:Key="SpeedButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ControlTemplate.Resources>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ControlTemplate.Resources>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}">
</ToggleButton>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="18px"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Background" Value="Gray"/>
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="DarkRed"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="BorderBrush" Value="White" />
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="LightSeaGreen"/>
</Trigger>
</Style.Triggers>
</Style>
EDIT:
I think you had completely different problem. Trigger on IsMouseOver or IsChecked doesn't get rid of default Windows hover colours. So you have to get rid of that, which is achieved but templating ToggleButton with a Border on top of templating your RadioButton with that styled ToggleButton. Note that if you would want to modify it more, you have to bind it's properties in the Border as well in ToggleButton. Difference between TemplateBinding and Binding on TemplatedParent is here and furthermore TemplateBinding is only One Way, so IsChecked should be on TemplatedParent.
This should work now (at least for me it did in new project), just replace the colours.
The following is an example of how to set the background when the cell is selected, but when I actually click inside the cell to edit it the color changes. Is there a trigger property for when a cell is being edited? I'd like the background not to change.
<DataGrid Name="DG1" ItemsSource="{Binding}" SelectionUnit="Cell" >
<DataGrid.CellStyle>
<Style TargetType="DataGridCell" >
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="SeaGreen"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
Answering my own question, it looks like the Cell background color is based off of SystemColors.WindowBrushKey. Overriding that resource like such <SolidColorBrush x:Key="{x:Static SystemColors.WindowBrushKey}" Color="Red" /> did the trick.
`
You can add another trigger into your existing style for the IsEditing state. Then you can set the ControlTemplate for the DataGridCell inside of the trigger.
<Trigger Property="IsEditing" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text, Mode=TwoWay, UpdateSourceTrigger=Default}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="0" BorderThickness="0" Background="SeaGreen"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
This works with DataGridTemplateColumn as well as other types like DataGridTextColumn, DataGridCheckboxColumn and etc.:
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border x:Name="CellBorder"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter TargetName="CellBorder" Property="BorderBrush" Value="Green" />
<Setter TargetName="CellBorder" Property="Background" Value="yellow" />
<Setter TargetName="CellBorder" Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>