Code reusability in Styles - wpf

I have a style element which has minor edits and mostly repeated. How do i make it more generic - so setter properties are set within based on value versus repeating the code twice
<ResourceDictionary>
<Style x:Key="TextBlockStyleEnvironment" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,4,0,0" />
<Setter Property="FontSize" Value="8" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style x:Key="TextBlockStyleLocation" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,4,0,0" />
<Setter Property="FontSize" Value="10" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</ResourceDictionary>
As you see from the code all setter properties are same except Margin and FontSize. Also attached is the screenshot of it on rendering.
Please note - want to keep this self contained within a Style and not have declare at local level in XAML when this being consumed.
Possible values of Env can be Dev,QA,Prod and possible values of location can be TK, LN
Consuming in XAML snippet as follows:
<Grid DataContext="{....}">
<StackPanel>
<TextBlock Text="{Binding Environment}" Style="{StaticResource TextBlockStyleEnvironment}"/>
<TextBlock Text="{Binding Location}" Style="{StaticResource TextBlockStyleLocation}"/>

You can use style inheritance:
<ResourceDictionary>
<Style x:Key="BaseTextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,4,0,0" />
<Setter Property="FontSize" Value="8" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style x:Key="TextBlockStyleEnvironment" BasedOn="{StaticResource BaseTextBlockStyle}" TargetType="{x:Type TextBlock}" />
<Style x:Key="TextBlockStyleLocation" BasedOn="{StaticResource BaseTextBlockStyle}" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="10" />
</Style>
</ResourceDictionary>
Additionally you can create attached properties and bind to those from within your control templates. This gives you the flexibility of not having to create hundreds of styles just because something minute needs to be different.
A good example of that is a button that has an image. When the mouse is over the image, the image needs to change. Typically you'd have to create a new control template/style for each button that implements that behavior. However, if you create two attached properties - NormalStateImageSource and MosueOverImageSource, you can bind to those in your control template. This allows you to have a single full blown style for the button, and later to declare individual styles for other buttons that only change the values of these attached properties.

There are few techniques in WPF world.
First, if styles are same type, it's possible to use BasedOn attribute:
<Style x:Key="GenericTextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,4,0,0" />
<Setter Property="FontSize" Value="8" />
</Style>
<Style x:Key="TextBlockStyleEnvironment"
BasedOn="{StaticResource GenericTextBlockStyle}"
TargetType="{x:Type TextBlock}">
</Style>
However the BasedOn attribute can get really messy sometimes. It's also possible to do it this way, which will work for elements that are not the same type:
<Thickness x:Key="LeftBorderMargin" Top="10" />
<Style x:Key="TextBlockStyleEnvironment" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="{StaticResource LeftBorderMargin}" />
</Style>
It's common practice to refactor all the colors / margins out from the Style element, for reusability.

Related

How do I apply styles to different `TextBlock` objects?

Here is my Application ResourceDictionary
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CABI_PO_Manager.Themes">
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock"
x:Key="YellowTextBlock">
<Setter Property="FontSize" Value="16"/>
<Setter Property="Foreground" Value="#d8b243"/>
</Style>
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock"
x:Key="GreenTextBlock">
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock"
x:Key="RedTextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="#a01e21"/>
</Style>
</ResourceDictionary>
I want to have some default styles for a TextBlock which works
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
I found somewhere to use x:Key but I can't get it to work.
I will have several TextBlock's, How do I identify a TextBlock in the UI XAML as Red, Yellow or Green TextBlock and apply that style to them? This isn't recognized x:Key="GreenTextBlock"
<TextBlock x:Key="GreenTextBlock" Grid.Column="1" Margin="0,10,0,0" TextWrapping="Wrap" Text="PO Manager" VerticalAlignment="Top" TextAlignment="Center" FontWeight="ExtraBold"/>
If u want apply style on for example all TextBlocks in application, just use style without x:key defined e.g
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
When you are applying a style, use TargetType="{x:Type TextBlock}" instead of TargetType="TextBlock"
When do you use want to base on other style use
BasedOn="{StaticResource StyleUWantToBaseOn}"
where StyleUWantToBaseOn is style with x:Key property
And when you want to apply a specific style on lets say textblock you want to use Style property e.g:
<TextBlock Style="{StaticResource GreenTextBlock}" Grid.Column="1" />

Can I apply a WPF style to an element only in a certain layout?

I have a TextBlock style like this:
<Style TargetType="TextBlock" x:Key="FormLabel">
<Setter Property="Height" Value="20" />
<Setter Property="Margin" Value="10" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
I use it in Grid based forms, e.g:
<TextBlock Text="Code" Grid.Row="1" Grid.Column="0" Style="{StaticResource FormLabel}" />
Now instead of repeating the style name on every TextBlock in the grid, I would prefer to e.g. have a Grid style like:
<Style TargetType="Grid" x:Key="FormGrid">
<Setter Property="Width" Value="400" />
...
</Style>
Then I would, if possible, like to modify my TextBlock style to only apply to that element when it is a child element of a Grid with style FormGrid.
Is this possible, and if so, how can I achieve it?
This is indeed possible by using an implicit style within another style as a resource. Take this example:
...
<Window.Resources>
<Style x:Key="FormGrid" TargetType="Grid">
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="Height" Value="20" />
<Setter Property="Margin" Value="10" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Style.Resources>
<Setter Property="Width" Value="400" />
</Style>
</Window.Resources>
<StackPanel>
<Grid Style="{StaticResource FormGrid}">
<TextBlock Text="This text block is styled with FormGrid TextBlock implicit style."/>
</Grid>
<TextBlock Text="This text block uses the default style."/>
</StackPanel>
...
This is not possible by out of the box WPF abilities. What you are looking here is CSS like style selectors. WPF Only allows style inheritance through BasedOn property. I am not sure if this could be an alternative, but you can define that specific TextBlock style as part of that grid resources and target to match any textblock inside of it.
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Height" Value="20" />
<Setter Property="Margin" Value="10" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Grid.Resources>

How to Change the Style on AvalonEdit CodeCompletion Window?

I'm trying to figure out how to change the style of the AvalonEdit CodeCompletion window. However, I can't figure out the right combination of xaml style target/properties to change it. The main thing I'd like to do is get rid of the border, but maybe some additional changes as well.
Here is the xaml I've tried. None of it is affecting the UI.
xmlns:ae="clr-namespace:ICSharpCode.AvalonEdit.CodeCompletion;assembly=ICSharpCode.AvalonEdit"
<Style TargetType="{x:Type ae:CompletionWindow}">
<Setter Property="WindowStyle" Value="None" />
</Style>
<Style TargetType="{x:Type ae:CompletionWindowBase}">
<Setter Property="WindowStyle" Value="None" />
</Style>
<Style TargetType="{x:Type ae:CompletionListBox}">
<Setter Property="Background" Value="Red" />
</Style>
<Style TargetType="{x:Type ae:CompletionList}">
<Setter Property="Background" Value="Orange" />
</Style>
Use this style to remove border on window:
<Style TargetType="{x:Type avalonEdit:CompletionWindow}">
<Setter Property="WindowStyle" Value="None"></Setter>
<Setter Property="ResizeMode" Value="NoResize"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
</Style>
To make the styles affect the UI, you can put them in a resource dictionary xaml and parse that with (ResourceDictionary)XamlReader.Parse(ResourcesAsXaml).
Then assign the ResourceDictionary to the Resources property of the CompletionWindow.

Can I override/replace a property of a child-item in a default style

The default wpf MenuItem (on Menu) is constructed out of controls approx. like this:
grid; outer-rectangle; bg-rectangle; inner-rectangle; dockpanel; popup.
The dockpanel in turn consists of:
contentpresenter[icon]; path; contentpresenter[text]
The contentpresenter[text] consists of a TextBlock control.
What I want to achieve is to define a Style, as simple as possible, to change the VerticalAlignment property of this TextBlock, but only for the TextBlock in MenuItem, not in general.
<Style x:Key ="TextBlockCenterStyle" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style TargetType="MenuItem">
<Setter Property="FontSize" Value="11" />
<Setter Property="ItemContainerStyle" Value="TextBlockCenterStyle" />
<Style.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Style.Resources>
</Style>
I tried Style.Resources and ItemContainerStyle.
Cannot get it to work. ItemContainerStyle throws TargetInvocationException (from NullReferenceException) at run-time.
When it is possible it should be a general solution, something like FindChildControl?!
Did you try ItemContainerStyle?
Something like:
<MenuItem ItemContainerStyle = {StaticResource MyItemContainerStyle}../>
Then the MyItemContainerStyle have your
<Style x:Key ="MyItemContainerStyle" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
=============== answer after EDIT ======================
try this:
<Style TargetType="MenuItem">
<Setter Property="FontSize" Value="11" />
<Setter Property="ItemContainerStyle" Value="{StaticResource TextBlockCenterStyle}" />
<Style.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Style.Resources>
</Style>

Style breaks when x:Key is added

Under <Window.Resources>, I have the following style defined:
<Style TargetType="TextBox">
<Setter Property="Height" Value="22" />
<Setter Property="Width" Value="125" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="WhiteSmoke" />
</Style>
It works fine until I needed to inherit the style on another style
<Style BasedOn="{StaticResource TextBoxStyle}" TargetType="{x:Type PasswordBox}">
Which means I need to add the x:Key=TextBoxStyle to the Text box style above.
But when I do this, the styling for the text box breaks altogether.
I tried doing the same to Button styling, and the same thing happens, where the style will break if I add a key to it.
The only solution I thought of is to individually add the style to the elements, but that is what I am trying not to do.
No, you do not need to add x:Key to reference it:
<Style BasedOn="{StaticResource {x:Type TextBox}}" TargetType="{x:Type PasswordBox}">
Well to provide a better understandability and maintainance, I would prefer the following approach. IMHO, another programmer could get better into the code, if the implicities are reduced to a minimum.
<Style x:Key="BasicTextBoxStyle" TargetType="{x:Type TextBox}">
<!--some settings here-->
</Style>
<!--Declare BasicTextBoxStyle as default style for TextBoxes-->
<Style BasedOn="{StaticResource BasicTextBoxStyle}" TargetType="{x:Type TextBox}"/>
<!--Create some special style based on the basic style-->
<Style BasedOn="{StaticResource BasicTextBoxStyle}" TargetType="{x:Type PasswordBox}">
<!--some more specific settings-->
</Style>
Just my two cents...

Resources