Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Defining the Default Style for a Lookless Control

, 14 Apr 2010 CC (Attr 3U)
Rate this:
Please Sign up or sign in to vote.
Microsoft introduced the concept of “lookless” controls in WPF, which means that the control defines its behaviour without any information about how it actually looks, which is where templates and styles come in. However, all of the default controls provide a default look, which is in keeping with

Microsoft introduced the concept of “lookless” controls in WPF, which means that the control defines its behaviour without any information about how it actually looks, which is where templates and styles come in. However, all of the default controls provide a default look, which is in keeping with the current Windows theme (Aero in Windows Vista, Luna, Metallic, or Homestead in Windows XP and so on). If you are creating lookless controls in a class library for general consumption, it would be helpful to also provide a default look for your controls. To achieve this, there are three things that you need to do.

Note: This article refers specifically to custom controls (i.e. those that inherit from Control, FrameworkElement or similar, not user controls that inherit from UserControl). For more information about creating controls for WPF, see Control Authoring Overview on the MSDN web site.

1. Override the Metadata for the DefaultStyleKey Property

In a static constructor for your control, set the default style key to the type name, which is used to do style lookups for your control. The following code example shows the static constructor for a Wizard class that I’m creating.

   1:  static Wizard()
   2:  {
   3:      DefaultStyleKeyProperty.OverrideMetadata(
   4:          typeof(Wizard),
   5:          new FrameworkPropertyMetadata(typeof(Wizard)));
   6:  }

2. Define Your Default Style

You can create a default style for each of the different Windows themes. To do this you need to create resource dictionary with a specific name in a Themes folder that is a subfolder of the folder that contains your control in your class library. The following table provides the file names of the theme-specific resource dictionaries.

Resource dictionary Windows theme
Classic.xaml “Classic” Windows 9x/2000 look on Windows XP
Luna.NormalColor.xaml Default blue theme on Windows XP
Luna.Homestead.xaml Olive theme on Windows XP
Luna.Metallic.xaml Silver theme on Windows XP
Royale.NormalColor.xaml Default theme on Windows XP Media Center Edition
Aero.NormalColor.xaml Default theme on Windows Vista

You don’t need to provide a style for every theme. If there is no theme-specific resource, then the generic resource is used for the control, which is defined in the Themes\generic.xaml resource dictionary.

The following XAML code example shows the default style in generic.xaml for the Wizard class described above.

   1:  <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:                      xmlns:local="clr-namespace:DerekLakin.Libraries.Presentation"
   3:                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   4:      <Style TargetType="{x:Type local:Wizard}">
   5:          <Setter Property="Template">
   6:              <Setter.Value>
   7:                  <ControlTemplate TargetType="{x:Type local:Wizard}">
   8:                      <DockPanel LastChildFill="True">
   9:                          <WrapPanel DockPanel.Dock="Bottom"
  10:                                     HorizontalAlignment="Right"
  11:                                     Orientation="Horizontal"
  12:                                     Margin="10">
  13:                              <Button x:Name="PART_Back"
  14:                                      Content="Back" />
  15:                              <Button x:Name="PART_Next"
  16:                                      Content="Next"
  17:                                      Margin="4,0,0,0" />
  18:                              <Button x:Name="PART_Cancel"
  19:                                      Content="Cancel"
  20:                                      Margin="4,0,0,0" />
  21:                              <Button x:Name="PART_Finish"
  22:                                      Content="Finish"
  23:                                      Margin="4,0,0,0" />
  24:                          </WrapPanel>
  25:                          <Frame x:Name="PART_Frame"
  26:                                 NavigationUIVisibility="Hidden" />
  27:                      </DockPanel>
  28:                  </ControlTemplate>
  29:              </Setter.Value>
  30:          </Setter>
  31:      </Style>
  32:  </ResourceDictionary>

3. Add the ThemeInfo Attribute to AssemblyInfo

The final part is to publicise the fact that your assembly contains control-specific resources, which you do by using the ThemeInfo attribute. The GenericDictionaryLocation property defines where the generic resources are and the ThemeDictionaryLocation property defines where the themed resources are. The value for both properties is a ResourceDictionaryLocation enumeration, which is None, SourceAssembly, or ExternalAssembly.

The following code example shows the ThemeInfo attribute for the class library project that contains the Wizard class described previously, which has no theme-specific resources, but does specify a generic resource.

   1:  [assembly: ThemeInfo(ResourceDictionaryLocation.None,
   2:      ResourceDictionaryLocation.SourceAssembly)]

Side Note

If your custom control has specific parts that are key to the operation of the control (such as the buttons and frame in the Wizard example described previously), then the convention is to name them using the “PART_” prefix in the control template. For each required part in your template, you should add a TemplatePart attribute to the class definition as shown in the following code example.

   1:  [TemplatePart(Name = "PART_Frame", Type = typeof(Frame))]
   2:  [TemplatePart(Name = "PART_Back", Type = typeof(Button))]
   3:  [TemplatePart(Name = "PART_Cancel", Type = typeof(Button))]
   4:  [TemplatePart(Name = "PART_Finish", Type = typeof(Button))]
   5:  [TemplatePart(Name = "PART_Next", Type = typeof(Button))]
   6:  public class Wizard : Window

You should get a reference to these named template parts in the OnApplyTemplate override of your class because this is the point where the control template has actually been applied. It is common not to specifically handle missing template parts so that an exception (typically a NullReferenceException) gets raised at runtime, which clearly indicates that something is broken.

The following code example shows the OnApplyTemplate override for the Wizard class described previously.

   1:  public override void OnApplyTemplate()
   2:  {
   3:      base.OnApplyTemplate();
   4:   
   5:      // Get the template parts.
   6:      this.navigationFrame = this.GetTemplateChild("PART_Frame") as Frame;
   7:      this.back = this.GetTemplateChild("PART_Back") as Button;
   8:      this.cancel = this.GetTemplateChild("PART_Cancel") as Button;
   9:      this.finish = this.GetTemplateChild("PART_Finish") as Button;
  10:      this.next = this.GetTemplateChild("PART_Next") as Button;
  11:  }

For more information about designing lookless controls, see Guidelines for Designing Stylable Controls on the MSDN web site.

This work is licensed under a Creative Commons Attribution By license.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution 3.0 Unported License

Share

About the Author

Derek Lakin
Software Developer (Senior)
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411028.1 | Last Updated 15 Apr 2010
Article Copyright 2009 by Derek Lakin
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid