Welcome to

Magenic Technologies Community Blog

Sign in | Join | Help

Outsourcing to Sleestak

Technical Blog on ASP.NET AJAX, WCF, WPF, and any other shiny tinsel that captures a developer's short attention span, with hands on information about getting the most productivity out of your sleestak workforce.

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>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

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>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }will fortuitously render itself such that both the base ComboBox as well as the CustomComboBox receive the style specified in the “ComboBoxStyle” style definition.

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>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }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">
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Unfortunately, the typed style does not understand that my CustomComboBox inherits from the ComboBox type. While the base ComboBox is rendered with my style,  the appearance of the CustomComboBox reverts to the default ComboBox specified by WPF for my OS.

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"/>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Typically the BasedOn attribute is used to extend a certain 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 set 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.

Published Sunday, July 12, 2009 8:00 PM by Anonymous

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments

Leave a Comment

(required) 
required 
(required) 
Powered by Community Server, by Telligent Systems