次の方法で共有


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
 
 One
 
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

Two

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

Three

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

Four

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