Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Lookless Controls / Themes

0.00/5 (No votes)
17 Jun 2009 1  
Lookless controls / themes

One of the great things about WPF is that it separates the functionality of a control from the way it looks, this has become known as “lookless controls”. Which is great, but how can we ensure that our custom controls behave and also have a default look in the first place. This mini article will try and show you what happens with lookless controls.

Ensuring Control Does What We Want It To Do

I will firstly talk a bit about how you can create a split designer/developer project that should work correctly.

The first things that a developer should do is create a TemplatePartAttribute such that this is captured in the metadata which can be used by an documentation tool. By using this TemplatePartAttribute, the developer is able to tell the designer what was intended for a correct control operation.

Here is an example for a small control that I have made:

1:      [TemplatePart(Name = "PART_DropDown",
2:        Type = typeof(ComboBox))]
3:      public class DemoControl : Control
4:      {
5:      }

This should be an alert to the designer that they need to create a part of the control Template that should be a ComboBox and should be called “PART_DropDown”. So that is part 1, next the developer should override the OnApplyTemplate and look for any expected parts that are required to make the control work properly and wire up the events required. Here is an example.

 1:          public override void OnApplyTemplate()
 2:          {
 3:              base.OnApplyTemplate();
 4:
 5:              //Obtain the dropdown and create the items
 6:              dropDown =
 7:                  base.GetTemplateChild(
 8:                  "PART_DropDown") as ComboBox;
 9:              if (dropDown != null)
10:                  dropDown.SelectionChanged +=
11:                      new SelectionChangedEventHandler(
12:                          dropDown_SelectionChanged);
13:
14:
15:          }
16:
17:          void dropDown_SelectionChanged(object sender,
18:              SelectionChangedEventArgs e)
19:          {
20:
21:
22:          }

Another method is to rely on RoutedCommands that should be used by the designer in the XAML control Template. These can then be used as follows:

1:
2:              // Make sure the command is bound, so that it will work when called to
3:              CommandBindings.Add(new
4:                  CommandBinding(DemoCommands.SayHello,
5:                  //The actual command handler code
6:                  (s, e) => {
7:                      MessageBox.Show("Hello");
8:                  }));

Lookless Controls

In order to create a truly lookless control, we should do the following:

Override the default Style associated with a control, this is done by changing the metadata. An example of which is as follows:

1:          static DemoControl()
2:          {
3:              //Provide a default set of visuals for a custom control
4:              DefaultStyleKeyProperty.OverrideMetadata(
5:                  typeof(DemoControl),
6:                  new FrameworkPropertyMetadata(
7:                      typeof(DemoControl)));
8:          }

Next, we need to understand a few things about how Themes work in WPF. There is an assembly level attribute that is called ThemeInfoAttribute, which is typically created as follows:

 1:  [assembly: ThemeInfo(
 2:      ResourceDictionaryLocation.None,
 3:      //where theme specific resource dictionaries are located
 4:      //(used if a resource is not found in the page,
 5:      // or application resource dictionaries)
 6:      ResourceDictionaryLocation.SourceAssembly
 7:      //where the generic resource dictionary is located
 8:      //(used if a resource is not found in the page,
 9:      // app, or any theme specific resource dictionaries)
10:  )]

This could be used to indicate a location for a Style for a control. More often than not, this is created as I have just shown. If you do not specify an external DLL to look in, the next place that is examined is Themes\generic.xaml, so this is where you should put your default Style/Template for your custom control.

So typically, you would create a generic.xaml file that held the default control Style/Template.

For the attached demo project, my generic.xaml simply contains a bunch of merged resource dictionary objects as follows:

 1:  <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 2:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 3:
 4:      <!– Merge in all the available themes >
 5:      <ResourceDictionary.MergedDictionaries>
 6:          <ResourceDictionary
 7:              Source="/CustomControls;component/Themes/Default.xaml" />
 8:          <ResourceDictionary
 9:              Source="/CustomControls;component/Themes/Blue.xaml" />
10:          <ResourceDictionary
11:              Source="/CustomControls;component/Themes/Red.xaml" />
12:      </ResourceDictionary.MergedDictionaries>
13:
14:
15:
16:  </ResourceDictionary>

If we study one of these, a little more closely, say the “Blue” one, we can see that also uses a ComponentResourceKey markup extension.

 1:  <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 2:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 3:      xmlns:local="clr-namespace:CustomControls">
 4:
 5:  <Style x:Key="{ComponentResourceKey {x:Type local:DemoControl}, Blue }"
 6:         TargetType="{x:Type local:DemoControl}">
 7:      <Setter Property="Background" Value="Blue"/>
 8:      <Setter Property="Margin" Value="10″/>
 9:      <Setter Property="Template">
10:          <Setter.Value>
11:              <ControlTemplate TargetType="{x:Type local:DemoControl}" >
12:                  <Border Background="{TemplateBinding Background}"
13:                          CornerRadius="5″ BorderBrush="Cyan"
14:                          BorderThickness="2″>
15:                      <StackPanel Orientation="Vertical"
16:                                  Margin="{TemplateBinding Margin}">
17:                          <Button x:Name="btnSayHello"
18:                                  Margin="{TemplateBinding Margin}"
19:                                  Background="LightBlue"
20:                                  Foreground="Black"
21:                                  Command="{x:Static
22:                                  local:DemoCommands.SayHello}"
23:                                  Content="Say Hello" Height="Auto"
24:                                  Width="Auto" />
25:                          <ComboBox x:Name="PART_DropDown"
26:                                    Margin="{TemplateBinding Margin}"
27:                                    Background="LightBlue"
28:                                    Foreground="Black">
29:                              <ComboBoxItem Content="Blue"/>
30:                              <ComboBoxItem Content="Red"/>
31:                          </ComboBox>
32:                      </StackPanel>
33:                      <Border.LayoutTransform>
34:                          <ScaleTransform CenterX="0.5″
35:                                          CenterY="0.5″
36:                                          ScaleX="3.0″
37:                                          ScaleY="3.0″/>
38:                      </Border.LayoutTransform>
39:                  </Border>
40:              </ControlTemplate>
41:          </Setter.Value>
42:      </Setter>
43:  </Style>
44:
45:
46:  </ResourceDictionary>

So let's get to the bottom of that. What does that do for us. Well quite simply, it allows us another way to select a resource, by using a Type/Id to lookup the resource. Here is an example:

1:              Style style = (Style)TryFindResource(
2:                  new ComponentResourceKey(
3:                      typeof(DemoControl),
4:                      styleToUseName));
5:
6:              if (style != null)
7:                  this.Style = style;

The working app simply allows users to toggle between 3 different Styles for the lookless control. You can download it and play with using the demo project.

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here