Click here to Skip to main content
15,880,905 members
Articles / Desktop Programming / WPF

Lookless Controls / Themes

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
17 Jun 2009CPOL3 min read 32K   335   5   2
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:

C#
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.

C#
 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:

C#
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:

C#
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:

C#
 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:

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

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

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionStatic key? Pin
SilentMember3-May-12 21:42
SilentMember3-May-12 21:42 
Very interesting article thank you!

Whats the advantage of using ComponentResourceKey over static keys?


Could just as well be
<Style x:Key="DemoControlBlue" ...>

You would apply it almost exactly the same way.

Nick
AnswerRe: Static key? Pin
Sacha Barber3-May-12 21:57
Sacha Barber3-May-12 21:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.