Devlico.Us
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @devlicious

Christopher Bennage

Our WPF book is now available!

June 2008 - Posts

  • Contrasting Control Templates & Styles in WPF

    We've had a few projects that involved custom styling for many of the standard controls. At first, I found myself swimming in a sea of XAML and rather double-minded about where to put things. Now, an approach has begun to coalesce for me.

    When it comes to customizing the look of controls, there are two concerns: styles and control templates. Knowing when to use one or the other can be confusing, as there is really a lot of overlap in what they each do.

    Styles

    Styles are frequently compared to cascading styles sheets (css) in the html world. WPF styles are like css in that they can be used to globally define the looks of certain elements, they can be paired to specific element by way of a key, or they can even be declared inline directly on an element.

    I've come to view styles as a way to set default values on the public properties of WPF elements. (Really, it's dependency properties, but most public properties on WPF controls are dependency properties anyway .) When you associate a style with an element you are essentially saying "here's the set of properties and corresponding values that I'd like to set." This distinction is important for two reasons:

    • Styles can only affect the public dependency properties.
    • Styles can affect non-visual properties.

    This concept of setting properties is inherent in the syntax of styles:

    <Style>
        <Setter Property="X" Value="Y" />
    </Style>

    This also means that styles are limited in what they can do by the public API of the element that they are applied to.

    Control Templates

    Control templates are used for manipulating the composition of a control.  They are a means of modifying the structure of a control.

    First, you need to recognize that every control in WPF is composed out of other WPF elements. If you were to examine a Button control, you would discovered that internally it's composed of something like this:

    <ms:ClassicBorderDecorator x:Name="ContentContainer"
                               SnapsToDevicePixels="True"
                               Background="{TemplateBinding Background}"
                               BorderBrush="{TemplateBinding BorderBrush}"
                               BorderStyle="Raised"
                               BorderThickness="3,3,3,3">
        <ContentPresenter />
    </ms:ClassicBorderDecorator>
    

    ClassicBorderDecorator is an element Microsoft built for emulating the look of the existing Windows UI (that's pre-WPF elements). It's very much like a Border, and tucked away in the theme-specific assembly PresentationFramework.Classic.dll. (Ok, maybe that's a lame example).

    There are certain internal elements that the control looks for in its template.  In the case of Button, it look for a ContentPresenter, though it might be something else depending on the control.

    button exampleNow, let's say you want a button that has rounded corners, with a black background, and gray outline.  You can use a style to set the background and outline using the Background and BorderBrush properties, and you might hope for a CornerRadius property similar to that of a Border, but there isn't one. In short, you can't make a button have rounded corners using a style.

    I'm sure someone out there is thinking that you can use a style and set Background to a DrawingBrush that looks like that, and well, yes, you could, but um, that's a different matter and I need a simple example at the moment.

    We could achieve this with the following control template however:

    <Button>
        <Button.Template>
            <ControlTemplate TargetType="Button">
                <Border CornerRadius="4" 
                        Background="Black"
                        BorderBrush="Gray"
                        BorderThickness="1"
                        Padding="10 6">
                    <TextBlock Foreground="White"
                               Text="Click Me"/>
                </Border>
            </ControlTemplate>
        </Button.Template>
    </Button>

    You are afforded much more liberty with control templates than you are with styles, but the control templates are also trickier to use. In the template above, we completely bypassed the Content property on the button, and we manually created the "content" in the template.  That makes the template not so reusable and the above example is not a good template. Here's one that's a bit more generic:

    <Button Content="Click Me">
        <Button.Template>
            <ControlTemplate TargetType="Button">
                <Border CornerRadius="4" 
                        Background="Black"
                        BorderBrush="Gray"
                        BorderThickness="1"
                        Padding="10 6">
                    <ContentPresenter TextBlock.Foreground="White" />
                </Border>
            </ControlTemplate>
        </Button.Template>
    </Button>

    We replaced the TextBlock with a ContentPresenter. We use an attached property to make any text inside the ContentPresenter white.

    Why Both?

    So we've redefined the look of a Button, and we didn't touch the style.  Why do we need styles?

    There are a few good reasons:

    • Styles are easier to use. You don't need to be concerned about the internal composition of the control or making sure that you've included all the necessary pieces.
    • Styles can affect properties on the control that don't have anything to due with the composition.  That means they can affect things that control templates cannot.
    • If you want to globally replace a template for a control in your application, you have to do it with a style.
    • It's not uncommon to "break" your controls when you start creating custom templates. You don't need to worry about this so much with styles.

    More to come ...

  • Styling Separators in WPF

    Separators are the little tiddly-bits that, um, separate items in a menu or tool bar. Examples of SeparatorsThe intention is to divide the items on the menu or toolbar into logical groups. Separators are controls, but they don't have any real behavior.

    One of our current projects includes an exhaustive custom styling of a WPF application.  Last week, I was working on the top level menu as well as context menus.  I wanted all of my separators to look the same.  So I ignorantly created a style, the same way that I would for any other control. It looked something like this:

    <!-- Separator -->
    <Style TargetType="Separator">
        <Setter Property="Height" Value="1" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Separator}">
                    <Rectangle Height="{TemplateBinding Height}"
                               Fill="{StaticResource NormalBorderBrush}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    

    In case you're not familiar with it, WPF allows to specify a default style for a control by setting the TargetType and omitting the x:Key.

    But that doesn't work for separators.

    The reason is that MenuItem and ToolBar both use separators, and thus creating global style for both wouldn't make sense. (At least, I'm guessing that's what the designers of WPF were thinking.) Instead, both of these MenuItem and ToolBar expose a static property, SeparatorStyleKey. This acts as the key for locating the corresponding separator style.

    If you want to style the separators for all menu items, your need to set x:Key to this static property. The resulting style would look like this:

    <!-- Separator -->
    <Style x:Key="{x:Static MenuItem.SeparatorStyleKey}"  
           TargetType="{x:Type Separator}">
        <Setter Property="Height" Value="1" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Separator}">
                    <Rectangle Height="{TemplateBinding Height}"      
                               Fill="{StaticResource NormalBorderBrush}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
More Posts

Our Sponsors

Red-Gate!