WPF Refactoring with Styles
An issue that many WPF developers come across is the need to reuse Xaml across several different Xaml files. For example snippet 1 shows some Xaml I want to reuse in several other Xaml files. I could just cut and paste the Label and every ComboBox controls, but that would result in a lot of bloat and when I wanted to change the look of my Label or ComboBox I would have to update ever instance of the controls.
Snippet 1. – Window1.xaml
<Window x:Class="WPFRefactoring.Window1" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" Title="WPFRefactoring">
<Grid>
<StackPanel Orientation="Horizontal">
<Label Foreground="Blue" FontSize="20" FontFamily="Edwardian Script ITC">Can you read this font?</Label>
<ComboBox Name="comboBox1" Background="Tomato">
<TextBlock Text="Yes"/>
<TextBlock Text="No"/>
<TextBlock Text="What's the questions?"/>
</ComboBox>
</StackPanel>
</Grid>
</Window>
Figure 1. – Window1
WPF provides a few different ways to mitigate this problem. The first is to use a local style as shown in snippet 2. I simply migrate the properties that I want to be reused into a style for the control.
Snippet 2. – Window1.xaml using Styles.
<Window x:Class="WPFRefactoring.Window1" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" Title="WPFRefactoring">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Edwardian Script ITC"/>
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Tomato"/>
</Style>
</Grid.Resources>
<StackPanel Orientation="Horizontal">
<Label>Can you read this font?</Label>
<ComboBox>
<TextBlock Text="Yes"/>
<TextBlock Text="No"/>
<TextBlock Text="What's the questions?"/>
</ComboBox>
<Label Content="This Label uses the Style too."/>
</StackPanel>
</Grid>
</Window>
Figure 2. - Window1
This works well as I didn’t have to write the properties for the second label I added , but I want every Label and every ComboBox in my application to look this way not just those displayed in Window1.xaml. To do this I need put my style in the <Application.Resources> section of their MyApp.xaml file. This is shown in snippet 3. and the change to Window1.xaml is shown in snippet 4. I simply cut and pasted the styles from Window1.xaml into MyApp.xaml and deleted the resource section from Window1.xaml.
Snippet 3. – MyApp.xaml
<Application x:Class="WPFRefactoring.MyApp" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" StartupUri="Window1.xaml">
<Application.Resources>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Edwardian Script ITC"/>
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Tomato"/>
</Style>
</Application.Resources>
</Application>
Snippet 4. - Window1.xaml
<Window x:Class="WPFRefactoring.Window1" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" Title="WPFRefactoring">
<Grid>
<StackPanel Orientation="Horizontal">
<Label>Can you read this font?</Label>
<ComboBox>
<TextBlock Text="Yes"/>
<TextBlock Text="No"/>
<TextBlock Text="What's the questions?"/>
</ComboBox>
<Label Content="This Label uses the Style too."/>
</StackPanel>
</Grid>
</Window>
Not only does this allow me to reuse the Label and ComboBox Styles throughout my application, but it makes my Window1.Xaml file easier to read. Snippet 5 shows a new Xaml file that makes use of the same style. Notice that the writer of Window2.xaml doesn’t need to know about my application level styles.
Snippet 5. – Window2.xaml
<Window x:Class="WPFRefactoring.Window2" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" Title="WPFRefactoring">
<Canvas>
<StackPanel Orientation="Horizontal">
<Label>Style Still Works</Label>
<ComboBox>Same Here</ComboBox>
</StackPanel>
</Canvas>
</Window>
Figure 3. – Window 2
What if I wanted to have all of the dialogs in my application use a different style then the application level style? I could once again copy and paste, ever Label and every ComboBox style section, but this would still run into the update and bloat problem. The solution is to create a ResourceDictionary item and place my dialog styles in it. Then reference my style in my dialogs.
Snippet 6 shows my ResourceDictionary, note that it looks very similar to my application resources section in MyApp.xaml, but every Style must use a key, because this is a dictionary.
Snippet 6. – MyResourceDictionary.xaml
<ResourceDictionary xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005">
<Style x:Key="MyLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="Green"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Tahoma"/>
</Style>
<Style x:Key="MyComboBoxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</ResourceDictionary>
Snippet 7. – MyApp.xaml
<Application x:Class="WPFRefactoring.MyApp" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" StartupUri="Dialog.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyResourceDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Edwardian Script ITC"/>
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Tomato"/>
</Style>
</Application.Resources>
</Application>
Snippet 8. – Dialog.xaml
<Window x:Class="WPFRefactoring.Dialog" xmlns="https://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005" Title="WPFRefactoring">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}"/>
</Grid.Resources>
<StackPanel Width="250">
<Label>Resources are fun.</Label>
<ComboBox>Application Level ComboBox</ComboBox>
<ComboBox Style="{StaticResource MyComboBoxStyle}">My Overriden Style</ComboBox>
</StackPanel>
</Grid>
</Window>
Figure 4. – Dialog
Note that in snippet 8 I show two different ways to reference the styles defined in the MyResourceDictionary.xaml file. If I planned on having multiple Label’s or ComboBox’s or I wanted to use both the application level style and the style in my ResourceDictionary I would define a new style that is based on the style in my ResourceDictionary as shown in snippet 9.
Snippet 9.
<Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}"/>
</Grid.Resources>
If I was only going to use a style once in a file I would just reference the style directly.
Snippet 10.
<ComboBox Style="{StaticResource MyComboBoxStyle}">My Overriden Style</ComboBox>
Styles are a great way to reuse Xaml in a WPF application. They make it easier to update the look of an application, reduce the size of an application and reduce the complexity of reading xaml files.
Comments
- Anonymous
June 13, 2009
PingBack from http://gardenstatuesgalore.info/story.php?id=1871