How to Target WPF child controls with nested styles by name? - wpf

I am experimenting with creating a custom button in WPF.
I have basic XAML for a Button, with two TextBlock controls inside the button. One will be an image rendered by FontAwesome, and one will be text.
<TextBlock Style="{DynamicResource mediumLabel}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="4">SETTINGS</TextBlock>
<Button Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Style="{DynamicResource mainButton}" Template="{DynamicResource mainButtonTemplate}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="Image" Grid.Row="1" Grid.Column="0">cogs</TextBlock>
<TextBlock x:Name="Label" Grid.Row="1" Grid.Column="1" Text="SETTINGS" />
</Grid>
</Button>
I have global styles defined in App.xaml.
I can target each of these three elements individually, with individual styles in my App.xaml.
What I would like to do, I guess just for organization and ease of future use, I want to have a style for the Button, with nested styles to target each of the two TextBlock controls. Each will be styled differently, so I cant target the TextBlock type. I want to reference them by name.
I have tried referencing the main control_name.childcontrol_name, as well as just the control name.
I can't seem to get enough info on how to do this when searching, as I might be searching for the wrong terminology...
My Attempt at the nested style, with two attempts for the nested styles targeting.
<Style x:Key="mainButton" TargetType="Button">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="DarkSlateGray"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="DodgerBlue"/>
</Trigger>
</Style.Triggers>
<Style.Resources>
<Style TargetType="{x:Reference Image}">
<Setter Property="FontFamily" Value="/Genesis_desktop;component/tools/fontawesome-free-5.15.1-desktop/otfs/#Font Awesome 5 Free Solid"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="ForeGround" Value="orange"/>
</Style>
<Style TargetType="{x:Reference mainButton.Label}">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</Style.Resources>
</Style>

You are thinking the wrong way. It's not the Style that searches its target. It's the target that searches its Style.
The XAML parser creates an instance of the markup object e.g. a <TextBlock> and then, after checking the local FrameworkElement.Style property, traverses the logical tree to find if there is somewhere a Style defined that targets this instantiated type or that has a key, that matches the requested resource key.
What you want would be also quite inefficient as the name inside a namescope must be unique and namescopes are always quite "small". Such a element name driven mapping would require an explicit style that only matches a single control. And it would require to know the name of this control in advance.
This means you, the author of this individual Style, have direct access to the named control.
You could therefore define the style directly in the local ResourceDictionary e.g. TextBlock.Resources.
This has the same effect: the Style now applies to a single TextBlock element, that you know by name.
If you need to declare the exclusive style on a different parent ResourceDictionary or as merge resource, you usually name the Style by applying the x:Key directive and then request this resource explicitly e.g. by using StaticResource markup extension.
Also x:Reference returns a name and not a Type. The TargetType property is of type Type. This property uses a TypeConverter, which allows to assign a string value which is then converted to Type.
What you want is not possible by default. Styles are pure type specific (when defined as implicit resource) or are mapped explicitly by their x:Key value (as additional constraint). On the other hand, what you want is already there in a different form and can be achieved by using local resources or the x:Keydirective together with the StaticResource or DynamicResource markup extension.

The issue with your style is that the nested styles are defined in its Resources. These can only be accessed in the scope of this style. Consequently, the button cannot, because it has a different scope.
I do not think that it is possible to nest styles this way, but you could alternatively create a DataTemplate for the Content of the button and nest the styles there. Assign a key to them and reference them from your TextBlocks. In your mainButton style, you can add a setter to set the ContentTemplate to this template. You could also set the ContentTemplate on your button explicitly (but this would mean you have to move the data template out of the style). I hope this is the kind of nesting or encapsulation that you wanted to achive.
<Style x:Key="mainButton" TargetType="Button">
<Style.Resources>
<DataTemplate x:Key="MyButtoDataTemplate">
<DataTemplate.Resources>
<Style x:Key="ImageStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="/Genesis_desktop;component/tools/fontawesome-free-5.15.1-desktop/otfs/#Font Awesome 5 Free Solid"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="orange"/>
</Style>
<Style x:Key="LabelStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="Image" Grid.Column="0" Style="{StaticResource ImageStyle}">cogs</TextBlock>
<TextBlock x:Name="Label" Grid.Column="1" Style="{StaticResource LabelStyle}" Text="SETTINGS" />
</Grid>
</DataTemplate>
</Style.Resources>
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="DarkSlateGray"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="ContentTemplate" Value="{StaticResource MyButtoDataTemplate}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="DodgerBlue"/>
</Trigger>
</Style.Triggers>
</Style>
Your Button definition is simplified to this, as the style already defines everything:
<Button Grid.Column="1"
Grid.Row="5"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
Style="{DynamicResource mainButton}"
Template="{DynamicResource mainButtonTemplate}"/>

Related

One DataTrigger for multiple properties

I have a listview bound to an Observable collection, the listview has properties that most of them will use the same trigger.
is it possible to define the trigger once in the resource section and just refer to it once needed by the properties ?
so far i come to this :
<Style TargetType="TextBlock" x:Key="Pstyle">
<Setter Property="Text" Value="Testing"/>
<Style.Triggers>
<DataTrigger Binding="{Binding P1}" Value="Testing">
<Setter Property="Foreground" Value="DarkGreen"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
and in the listview member i just apply the defined style to the propertie P1
Style="{DynamicResource Pstyle}"
but how to apply the same defined trigger for let say P2, P3, P4...
Since you want to put your trigger logic on the content of the TextBlock, in my opinion you should use a Trigger targeting Text property, instead of a DataTrigger.
Check out this sample code:
<Style TargetType="TextBlock" x:Key="Pstyle">
<Style.Triggers>
<Trigger Property="Text" Value="Testing">
<Setter Property="Foreground" Value="DarkGreen"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
<TextBlock Name="MyTextBlock1" Text="MyTextBlock1Text" Style="{StaticResource Pstyle}"/>
<TextBlock Name="MyTextBlock2" Text="MyTextBlock2Text" Style="{StaticResource Pstyle}"/>
<TextBlock Name="MyTextBlock3" Text="MyTextBlock3Text" Style="{StaticResource Pstyle}"/>
as you can see there is only one single style applied to different Textblocks.
Then you should adapt this to your listview.

In WPF style how to correct bind parent checkbox property to a child?

I'm new to WPF styling and I cannot make next thing to work:
I've got a custom checkbox style with another checkbox in it. I need parent "IsChecked" value to change as "contolCheckBox" child in ControlTemplate changes.
Tryed different triggers but can't get to parent property.
For now I've got next xaml code:
<Style x:Key="CustomCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="27" />
<Setter Property="Foreground" Value="#FFBABAC7" />
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<CheckBox x:Name="contolCheckBox" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsChecked" Value="{Binding IsChecked, ElementName=contolCheckBox}"/>
<!---->
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Foreground" Value="#FF29E200"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Foreground" Value="Firebrick"/>
</Trigger>
</Style.Triggers>
</Style>
"IsChecked" value of control doesn`t changes.
What am I doing wrong?
Binding ElementName=contolCheckBox, Path=IsChecked
not working too.
I don't think controlCheckBox is within the name scope since it's declared within the ControlTemplate. So your binding isn't working. Instead, invert your binding and bind from the bottom up. What you're looking for in a sitation like this is template binding. Try this in yourControlTemplate ..
<CheckBox x:Name="contolCheckBox" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked, Mode=TwoWay}" />
Note the RelativeSoucebinding. This indicates that I want to bind to the parent of the template. This is the common way to bind underlying ControlTemplate controls to parent properties.

Shared styling in WPF

What if I wanted to make all TextBlock elements in UserControl, or section of a UserControl, have FontWeight="Bold" and TextAlignment="Right"? Is there some style I can set for TextBlock elements within a certain scope so I don't have to repeat all those attributes?
Yes, create a style with no x:Key and it will be applied to all items of the specified TargetType within that scope
For example, to make all TextBlock have FontWeight="Bold" and TextAlignment="Right" within a specific UserControl only, you can use something like this:
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="TextAlignment" Value="Right" />
</Style>
</UserControl.Resources>
If you put this in your resources, all your textblocks become the same.
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextAlignment" Value="Right"/>
</Style>
Alternatively, you can also subclass from TextBlock (say, BoldTextBlock) and use it as the target type. This is so you can use regular textblocks in the same control as special textblocks
<Style TargetType="{x:Type BoldTextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextAlignment" Value="Right"/>
</Style>

Style label host image and text

this is a two part question that probbably have a similar answer.
I want to create in a resource dictionary a style for a label that contains first an image and then the text. The text, as a TextBlock has it's own style (had no problems there). Here is what I have
Label Style:
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<TextBlock Style="{StaticResource TextBlockStyle}">
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TextBlockStyle:
<Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="25 0 0 2.5"/>
<Setter Property="Width" Value="Auto"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextDecorations" Value="Underline"/>
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
</Style.Triggers>
</Style>
Now my problem is when I add a new label to my Control (ex: Window) and specify the text (ex: Create), no text is shown.Something like:
<Label Style="{StaticResource LabelStyle}">Create</Label>
The text Create does not show, however if I put in my LabelStyle->TextBlock->text it shows, but it's no good since I want to change it for different labels. Is there a way to bind my Label text to my (Inside Style) TextBlock.Text???
My other question is the same, but for images, and Image.Source.
Thanks :-)
EDIT:
This is what I have now with H.B. answer implemented
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Grid>
<StackPanel Orientation="Horizontal">
<Image Source="/Resources/Create.png" />
<TextBlock Style="{StaticResource TextBlockStyle}" Text="{TemplateBinding Content}"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that this is in the Resource Dictionary. For the TextBlock it works great. But for the image it's a different story. I want the same as the 'Text="{TemplateBinding Content}' but for the Image.Source and set it's path in my control when I add the label. Probabbly since it's multiple content I'll have to write more code than I'd like, but I'll settle for the easiest, cleanest answer.
H.B. thanks again and as for the hyperlink, this is still in development, it's not going to be in anyway a hyperlink, just some custom menu button with some animation so it's not so boring for the user :P
Your Label.Template no longer links the Content property of the Label (which you set to "Create") to any internal part. To fix this you can for example bind the TextBlock.Text like this:
<ControlTemplate TargetType="Label">
<TextBlock Style="{StaticResource TextBlockStyle}"
Text="{TemplateBinding Content}"/>
</ControlTemplate>
(I just noticed that you make the Label look like a hyperlink, you do realize that there already is a class for that, right?)

How to inherit XAML style and override property of child element?

we just got started with XAML and are still fighting with basic issues:
Coming from CSS we'd like to define a generic button style with custom control template and then have a second style inherit everything from the first style using "basedon. This second style should then override properties such e.g. "foreground color" (which works) but also properties of child elements within our custom template such as the "background color" of e.g. an included border element etc. (which doesn't work).
What's the general approach to go about things like this? How far can we go with cascading styles?
Cheers!
You can use an inherited style with no key reference:
<Grid>
<Grid.Resources>
<!-- Definition of default appearance of a button -->
<Style TargetType="Button" x:Key="Default">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="FontFamily" Value="Segoe Black" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontSize" Value="32pt" />
<Setter Property="Foreground" Value="#777777" />
</Style>
<!-- Define general style that based is base on the default style of the button without a key reference-->
<Style TargetType="Button" BasedOn="{StaticResource Default}"/>
<!-- In sub style override the properties you need -->
<Style BasedOn="{StaticResource Default}" TargetType="Button" x:Key="SubButton" >
<Setter Property="FontSize" Value="8pt" />
</Style>
</Grid.Resources>
<Button Content="Main" Height="51" HorizontalAlignment="Left" Margin="154,72,0,0" Name="button1" VerticalAlignment="Top" Width="141" />
<Button Content="Sub" Style="{StaticResource SubButton}" Height="51" HorizontalAlignment="Left" Margin="154,162,0,0" Name="button2" VerticalAlignment="Top" Width="141" />
</Grid>
I have (In my WPF application) default (base) styles defined in ResourceDictionary in App.xaml (in startup project). For example for button as follows.
<Style TargetType="Button">
<Setter Property="Margin" Value="5"/>
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="FontSize" Value="16"/>
</Style>
In all views I use (by default) this general style (automatically inherited)! When I need to change or add some property in default style (defined in App.xaml) I create new style based on default style.
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<!-- change -->
<Setter Property="Margin" Value="10" />
<!-- add -->
<Setter Property="Foreground" Value="Red" />
</Style>
When I need hide or strongly redefined default style (in some view) I create new style (based on nothing).
<Style TargetType="Button"/>
You can, of course, continues in inheritance in App.xaml or in specific view. You can based new named style on default style and use new style by name. For example RedButton and GreenButton style.
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" x:Key="RedButton">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" x:Key="GreenButton">
<Setter Property="Foreground" Value="Green" />
</Style>
Etc...
NOTE: instead define your style in App.xaml you can use standalone library (dll) with styles only and ResourceDictionary from your library to App.xaml ResourceDictionary.MergedDictionaries.
The standard approach to making a customizable control template is to use TemplateBinding in the template to bind to properties of the control, and then to set those properties in the child styles.
For example, this creates a button template with a Border control, and binds the Background of the Border to the Background property of the Button. By setting the Background property of the Button in other styles, it changes the Background property of the Border.
<StackPanel>
<StackPanel.Resources>
<Style x:Key="BaseButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BlueButtonStyle" TargetType="Button"
BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Background" Value="Blue"/>
</Style>
<Style x:Key="RedButtonStyle" TargetType="Button"
BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Background" Value="Red"/>
</Style>
</StackPanel.Resources>
<Button Style="{StaticResource RedButtonStyle}">Red</Button>
<Button Style="{StaticResource BlueButtonStyle}">Blue</Button>
</StackPanel>
Many of the properties on Control are intended to be used in control templates, and won't affect other behavior if they are changed. They are BorderBrush, BorderThickness, Background, Padding, HorizontalContentAlignment, and VerticalContentAlignment.

Resources