Synchronizing Style Hierarchies with Control Hierarchies in WPF

WPF provides two ways to apply styles to control elements: by name and by type.  In the typical named style implementation, one declares both the name by which a style can be referenced as well as the control type to which it can be applied.  For instance, a style declaration for a ComboBox would look like this:

<Style x:Key="ComboBoxStyle" TargetType="ComboBox">
    ...
</Style>

When styles are applied in this way, the underlying WPF rendering mechanism understands type hierarchies.  Consequently, this style could also be written with a TargetType higher up in the Control type hierarchy and still work:

<Style x:Key="ComboBoxStyle" TargetType="Control">
   ...
</Style>

This is a handy feature when you want to build a custom control but want to preserve your styles.  For instance, the ComboBox control has no built-in ability to trigger commands.  If you want to use MVVM, however, this is quite a shortcoming.  You can overcome this problem by building a custom control based on the ComboBox type and implementing the ICommandSource interface to pick up the SelectionChanged event:

class CustomComboBox: ComboBox, ICommandSource
{
     ...
}

Since my CustomComboBox inherits from the WPF ComboBox, the named style I use with a TargetType of ComboBox will automatically be applied to it and the following XAML:

<Window x:Class="SampleWPFProject.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=System"
    xmlns:custom="clr-namespace:SampleWPFProject"
    Title="Window1" Height="300" Width="300">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="25"/>
        <RowDefinition Height="25"/>
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Name="stackPanel1" Orientation="Horizontal">
        <TextBlock Margin="0,0,10,0" Width="60">Employees</TextBlock>
        <ComboBox x:Name="cbEmployees" SelectedIndex="0" Width="200" 
                  Style="{StaticResource ComboBoxStyle}">
            <ComboBoxItem>John Adams</ComboBoxItem>
            <ComboBoxItem>Ben Franklin</ComboBoxItem>
            <ComboBoxItem>Pat Henry</ComboBoxItem>
        </ComboBox>
    </StackPanel>
    <StackPanel Grid.Row="1" Name="stackPanel2" Orientation="Horizontal">
        <TextBlock Margin="0,0,10,0" Width="60">Tasks:</TextBlock>
        <custom:CustomComboBox x:Name="cbTasks" SelectedIndex="0" Width="200" 
                   Style="{StaticResource ComboBoxStyle}">
            <ComboBoxItem>Write Constitution</ComboBoxItem>
            <ComboBoxItem>Write Declaration</ComboBoxItem>
            <ComboBoxItem>Go for a ride</ComboBoxItem>
        </custom:CustomComboBox>
    </StackPanel>
</Grid>
</Window>

will fortuitously render itself like this, with the nice curvy boarders specified in my “ComboBoxStyle” style definition:

styleSample

But what if I want to apply my custom style arbitrarily to all ComboBoxes in my application?  This is where typed styles are very handy.

I can change the “key” element of the ComboBoxStyle to apply it generically to all ComboBoxes like this:

<Style x:Key="{x:Type ComboBox}" TargetType="ComboBox">
   ...
</Style>

Inside my XAML, I would simply remove the reference to the named style:

<ComboBox x:Name="cbEmployees" SelectedIndex="0" Width="200">
   ...
<custom:CustomComboBox x:Name="cbTasks" SelectedIndex="0" Width="200">

Unfortunately, the typed style does not understand that my CustomComboBox inherits from the ComboBox type, and the rendered window looks like this, reverting to the default ComboBox styling for my custom class:

 styleExample2

I could simply copy the entire original style and create a new one that specifies the CustomComboBox as the key, instead.  There is a shorter way, however.  I can take advantage of the BasedOn attribute of the style resource.  Rather than copy the entire style, I will create a new style based the original style and set the key to our custom type:

<Style x:Key="{x:Type ComboBox}" TargetType="ComboBox">
   ...
</Style>

<Style BasedOn="{StaticResource {x:Type ComboBox}}"  
       x:Key="{x:Type custom:CustomComboBox}" 
       TargetType="ComboBox"/>

Typically the BasedOn attribute is used to extend a base style with certain changes: for instance, off of a base style that sets  Button backgrounds to blue, one might want to create another that overrides the background color and sets it to orange.

This example shows that it can be used in a rather different way, however, to take a pre-existing typed style and apply it to a different type.  In this particular case, it is being used to align custom styles that are part of an inheritance hierarchy with custom controls that are part of a related type hierarchy.

Should it become necessary to extend the CustomComboBox control with a new custom control, in turn, one should be prepared to similarly extend the style hierarchy using the BasedOn attribute in order to maintain synchronization between one’s presentation and one’s functionality.

7 thoughts on “Synchronizing Style Hierarchies with Control Hierarchies in WPF”

  1. Just want to say your article is as amazing. The clearness in your post is simply excellent and i could assume you are an expert on this subject. Fine with your permission allow me to grab your RSS feed to keep updated with forthcoming post. Thanks a million and please carry on the rewarding work.

  2. I like the valuable information you provide in your articles. I’ll bookmark your weblog and check again here frequently. I am quite sure I will learn many new stuff right here! Best of luck for the next!

  3. It’s hard to find knowledgeable people on this topic, but you sound like you know what you’re talking about! Thanks
    You should take part in a contest for one of the best blogs on the web. I will recommend this site!

  4. I cant believe youre not more popular because you definitely have the gift.
    very nice post, i certainly love this website, keep on it

Leave a Reply

Your email address will not be published. Required fields are marked *