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:
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.