When does using styles that are based on other styles become excessive? - wpf

As a parallel to DRY for code I don't like having the same property assignments in XAML. I've seen code examples where quite a bit of code can be consolidated as a style. Once that's done and cleaned up more code can be represented by another style that's based on the initial style that has some more specific edits, and so on. There comes a point where this leads to styles that are way to "clever". A change in one of the styles leads to a cascading affect to all those that depend on it. Is there a rule of thumb or general guideline to remember when using Style ... BasedOn={...}?
An example from Head First C# and using the WPF version. On page 754 they have the example shown below. As this is an introduction there are several property assignments that can be consolidated using styles.
<StackPanel Margin="20">
<TextBlock Foreground="White" FontFamily="Segoe" FontSize="20px"
FontWeight="Bold" Text="{Binding TeamName}" />
<TextBlock Foreground="White" FontFamily="Segoe" FontSize="16px"
Text="Starting Players" Margin="0,5,0,0"/>
<ListView Background="Black" Foreground="White" Margin="0,5,0,0"
ItemTemplate="{StaticResource PlayerItemTemplate}"
ItemsSource="{Binding Starters}" />
<TextBlock Foreground="White" FontFamily="Segoe" FontSize="16px"
Text="Bench Players" Margin="0,5,0,0"/>
<ListView Background="Black" Foreground="White" ItemsSource="{Binding Bench}"
ItemTemplate="{StaticResource PlayerItemTemplate}" Margin="0,5,0,0"/>
</StackPanel>
Refactoring using styles to remove duplicated property assignments. This shows an intermediary step where the Margin property could further be consolidated.
<StackPanel Margin="20">
<StackPanel.Resources>
<Style x:Key="whiteForground" TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
<Setter Property="FontFamily" Value="Segoe" />
</Style>
<Style TargetType="ListView">
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="White" />
</Style>
<Style x:Key="appliedMargin" TargetType="TextBlock" BasedOn="{StaticResource whiteForground}">
<Setter Property="Margin" Value="0,5,0,0" />
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource whiteForground}" FontSize="20px"
FontWeight="Bold" Text="{Binding TeamName}" />
<TextBlock Style="{StaticResource appliedMargin}" FontSize="16px"
Text="Starting Players" />
<ListView ItemTemplate="{StaticResource PlayerItemTemplate}"
ItemsSource="{Binding Starters}" />
<TextBlock Style="{StaticResource appliedMargin}" FontSize="16px"
Text="Bench Players" />
<ListView ItemsSource="{Binding Bench}"
ItemTemplate="{StaticResource PlayerItemTemplate}" />
</StackPanel>

I try to stick with only one level of basedon.
So base style, one inheritance (leaf) style only.
But, obviously, with set values overriding any in the control the leaf is used on.
At most another layer.
There are several reasons for this.
1)
It's a nightmare tracking complex inheritance - as you have probably realised.
2)
There used to be ( and probably still is ) a potential problem with resource dictionary chaining (rd).
What you'll want to do is have your styles in rd.
These often get big pretty quick.
So you split them up into a number of rd.
You want to base one on another so that needs to know about the base style.
You therefore merge the base one in within the leaf rd.
One layer of merge dictionaries like:
<ResourceDictionary
...
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Blaa.Validation.UILib;component/Resources/UIlibResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ErrorStyle}"/>
<Style TargetType="{x:Type DatePicker}" BasedOn="{StaticResource ErrorStyle}"/>
Here the combobxox style is basedon the errorstyle in UILibResources.
That works OK.
If a rd merges another rd that in turn merges another rd... and so on.
You can have mysterious problems which seem to come from a delay in merging.
I've seen intermittent errors and styling problems.
I think there is therefore best to keep that chaining shallow and the base resource dictionaries small.
This could have been fixed in recent versions of the framework but I've seen nothing mention it and there's very little changed in WPF for quite some time now.
3)
It's all too easy to find you need this level before that but it depends on something else if you try and get too "clever".
It'd be great if styles cascaded or there were mixins but they don't and we haven't.
So this basedon stuff is all we have and it's kind of clunky compared to the web.

Take a look at Dependency Property Value Precedence

Related

Rational creation of XAML markup for readibility and sanity

Disclaimer: Not sure what to put in the title to make it clear as the words to be used are the ones that I don't know (yet) and am asking about. Feel free to correct.
Imagine a scenario with GUI consisting of 4x3 inputs, where every input consists of a label and a textbox. At the moment, it's done by explicitly declaring all the components and each component has the for as follows.
<Label x:Name="Label1"
Content="Text1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10,210,0,0" />
<TextBox x:Name="TextBox1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
Height="23" Margin="10,241,0,0"
TextWrapping="Wrap" Text="TextBox" />
Is there a recommended way to generate those from "something else", like a template or such, that's governing all the common attributes in it, eliminating the need for me to type them in over and over again (well, those were autogenerated but still...)? The alignments and sizes are tedious...
As for the margins, perhaps there's a layouting functionality? I've googled it but the hits related to XAML I've got were either suspiciously weird or relying on code behind. Is that the way to go or is it doable from XAML straight off?
To properly configure your layout, you should use WPF Layout Controls. In order to make the grid layout, you can use Grid, UniformGrid, etc., depending on your needs.
In order to apply several properties to the all controls inside the layout control, you can define the Style in the Resources of that control, as was mentioned already:
<Grid>
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<!-- etc... -->
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<!-- Row definitions here. -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<!-- Column definitions here. -->
</Grid.ColumnDefinitions>
<!-- controls ... -->
<TextBox Text="{Binding YourProperty}"
Grid.Row="1"
Grid.Column="2"
/>
<!-- controls ... -->
</Grid>
Here the style will be applied to all TextBox's controls.
You're referring to a WPF "Style." With styles, you define a set of properties that will be the same between all instances of a control which use that style.
<Style x:Key="MyTextBoxStyle" TargetType="TextBox">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="23" />
<Setter Property="TextWrapping" Value="Wrap" />
<!-- etc... -->
</Style>
<!-- This textbox will default its property values to those defined above -->
<TextBox Style="{StaticResource MyTextBoxStyle}" />

Expression Blend, an ItemTemplate and an Implicit Style

I'm having an issue with Blend not rendering items in a DataTemplate styled implicity.
I've setup a basic project to replicate the issue. Below is the Xaml + ResourceDictionary for those with Eagle eyes to see what I'm doing wrong (if anything) and if you're really keen theres a link to the Zipped project below.
This is what renders when the application is run:
and this is what Blend presents:
<Color x:Key="TextColor1">#FF3631C4</Color>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="TextTrimming" Value="None"/>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource TextColor1}"/>
</Setter.Value>
</Setter>
</Style>
<Canvas x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<TextBlock Text="Textblock" Canvas.Left="44.954" Canvas.Top="49.305" />
<TextBlock Text="Textblock 2" Canvas.Left="44.954" Canvas.Top="86.284" />
<ListBox ItemsSource="{Binding Collection}" Canvas.Left="134.016" Canvas.Top="29.026" Height="154.275" Width="384.575">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Property1}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Canvas>
Complete Example Project (65kb) - WpfApplication2.zip
The problem can of course be solved by explictly setting the style, but in my main project this will cause a bit of a headache.
I've seen some comments on other posts around that Blend may have issues but nothing concrete.
Any thoughts / suggestions?
Thanks!
Edit:
I discovered that if I give my style an Explicit Key, I can then create an Implicit Style based on the Explicit like so:
<Style x:Key="TextBlockStyle1" TargetType="{x:Type TextBlock}">
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="TextTrimming" Value="None"/>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource TextColor1}"/>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockStyle1}" />
This then gives me the ability to add another Implicit Style as a Resource in my DataTemplate:
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockStyle1}"></Style>
</DataTemplate.Resources>
<TextBlock Text="{Binding Property1}" />
</DataTemplate>
This will then give me the blendability I'll need in my main Project but still doesn't quite answer my original question.
Firstly Blend is written in WPF and XAML.
So Blend has its own application style and as your application also defines global styles, in order not to merge them it will be applying them differently and there is probably a bug in the method they used to apply those styles.
This is my guess why this is happening.
It doesn't solve the problem though, but might help you to find out other workarounds.

In WPF can you base one DataTemplate on another like you can with a Style? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Datatemplate inheritance
I have several data types that are not subclasses, nor do they share an interface, but they do have common properties that I want to display in a XAML DataTemplate. That said, I know this is possible...
<!-- Basic Style Inheritance -->
<Style x:Key="FooStyle" TargetType="Foo" />
<Style x:Key="EnhancedFooStyle" TargetType="Foo" BasedOn="{StaticResource FooStyle}" />
<!-- Inheritance By Type -->
<Style x:Key="BaseItemStyle">
<Setter Property="Control.Background" Value="Yellow" />
</Style>
<!-- These three data types share the same 'BaseItemStyle' -->
<Style TargetType="ListBoxItem" BasedOn="{StaticResource BaseItemStyle}" />
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource BaseItemStyle}" />
<Style TargetType="TreeViewItem" BasedOn="{StaticResource BaseItemStyle}" />
But can we do something similar like this for data templates which don't have the BasedOn property?
<DataTemplate x:Key="CommonTemplate">
<!-- Common Stuff Goes Here -->
</DataTemplate>
<!-- These three datatypes share the same DataTemplate -->
<DataTemplate DataType="Foo1" BasedOn="{StaticResource CommonTemplate}" />
<DataTemplate DataType="Foo2" BasedOn="{StaticResource CommonTemplate}" />
<DataTemplate DataType="Foo3" BasedOn="{StaticResource CommonTemplate}" />
I know BasedOn isn't what we want here because it's not 'based on' but rather 'is' in this scenario, but not sure how to do that purely in XAML. As I'm writing this, I have an idea, but I feel using UserControl is cheating...
<UserControl x:Key="CommonTemplate" x:Shared="False">
<!-- Common Stuff Goes Here -->
</UserControl>
<!-- These three datatypes share the same DataTemplate -->
<DataTemplate DataType="Foo1" BasedOn="{StaticResource CommonTemplate}">
<StaticResource ResourceKey="CommonTemplate" />
</DataTemplate>
<DataTemplate DataType="Foo2" BasedOn="{StaticResource CommonTemplate}" />
<StaticResource ResourceKey="CommonTemplate" />
</DataTemplate>
<DataTemplate DataType="Foo3" BasedOn="{StaticResource CommonTemplate}" />
<StaticResource ResourceKey="CommonTemplate" />
</DataTemplate>
Thanks!
#Foovanadil, actually I think I came up with something better. My new approach not only avoids the extra binding (the one on the content presenter), but also removes the need to have the template be applied at all by that presenter as you're explicitly setting its contents. Both of these things should speed up your UI, especially in larger, more complex interfaces.
<Border x:Shared="False" x:Key="Foo" BorderBrush="Red" BorderThickness="1" CornerRadius="4">
<TextBlock Text="{Binding SomeProp}" />
</Border>
<DataTemplate x:Key="TemplateA">
<ContentPresenter Content="{StaticResource Foo}" />
</DataTemplate>
<DataTemplate x:Key="TemplateB">
<ContentPresenter Content="{StaticResource Foo}" />
</DataTemplate>
Important: Make sure to use the x:Shared attribute on your shared content or this will not work.
M

Setting VerticalAlignment property to all controls

My WPF UserControl contains two stack panels and each of them contains labels, text boxes and radio buttons.
I would like to set VerticalAlignment property to Center to all controls in my UserControl with as little code as possible.
Now I have following solutions:
brute force - put VerticalAlignment="Center" in each control
define one style for FrameworkElement and apply it directly
define styles for each type of the controls on user control (this needs 3 style definitions, but automatically applies style to the control)
These three solutions need too much code.
Is there any other way to write this?
I hoped that defining style for FrameworkElement would automatically set property to all controls, but it does not work.
Here is snippet of my current XAML (I omitted second, very similar stack panel):
<UserControl.Resources>
<Style x:Key="BaseStyle" TargetType="FrameworkElement">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource BaseStyle}" Text="Value:" />
<RadioButton Style="{StaticResource BaseStyle}">Standard</RadioButton>
<RadioButton Style="{StaticResource BaseStyle}">Other</RadioButton>
<TextBox Style="{StaticResource BaseStyle}" Width="40"/>
</StackPanel>
</Grid>
Edit:
Re Will's comment: I really hate idea of writing control formatting code in codebehind. XAML should be sufficient for this really simple user control.
Re Muad'Dib's comment: Controls I use in my user control are derived from FrameworkElement, so this is not an issue here.
I had come across the same conundrum awhile ago as well. Not sure if this is the "best" way, but it was easy enough to manage by defining your base style and then creating separate styles for each control on the page that inherited from the base style:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="300" Background="OrangeRed">
<Page.Resources>
<Style TargetType="FrameworkElement" x:Key="BaseStyle">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,5,0" />
</Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource BaseStyle}" />
<Style TargetType="RadioButton" BasedOn="{StaticResource BaseStyle}" />
<Style TargetType="TextBox" BasedOn="{StaticResource BaseStyle}" />
</Page.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Value:" />
<RadioButton>Standard</RadioButton>
<RadioButton>Other</RadioButton>
<TextBox Width="75"/>
</StackPanel>
</Grid>
</Page>

Accessing a WPF GroupItem text for conversion in a template

I'm customising the appearance of grouping in a ListBox. In ListBox.Resources, I have declared something like (formatting removed):
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel Orientation="Vertical">
<!-- Group label -->
<ContentPresenter />
<!-- Items in group -->
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The actual group label is not very readable and I'd like to use a value converter to make it more presentable. However I cannot find a way to obtain this text and convert it.
I figure that a Binding would let me use a converter.
I've tried replacing the ContentPresenter above with the likes of...
<TextBlock Text="{TemplateBinding Content}"/>
<TextBlock Text="{Binding}"/>
...and numerous other things, but to no avail. Any suggestions?
Well isn't that just typical. I found the answer shortly after posting...
<TextBlock Text="{Binding Path=Content.Name,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=GroupItem},
Converter={StaticResource MyConverter}}"/>
Sometimes just the process of actually asking the question draws the answer out of thin air. In this case looking at the source code of GroupItem in .NET Reflector did the trick.
Hope someone else finds this edge case useful. Still, it would be a lot nicer if GroupItem exposed a property for this directly.
I'll still award a correct answer to anyone who knows a nicer way of doing this.

Resources