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

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

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 part1, 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:          }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Another method is to rely on RoutedCommands that should be used by the designer in the XAML control Template. These can then 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:                  }));

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

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:

 

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

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

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }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:  )]

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }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 Themesgeneric.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>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }If we study one of these, a little more closely, say the “Blue” one, we can see that is 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>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }So lets 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;

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

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, theming.zip

Enjoy.

Advertisements

8 thoughts on “Lookless Controls / Themes

  1. David says:

    What would you do if a essential PartTemplate is not available?

    public override void OnApplyTemplate()
    {
    base.OnApplyTemplate();
    dropDown = base.GetTemplateChild(
    “PART_DropDown”) as ComboBox;
    if (dropDown != null)
    {

    }
    else
    {
    //WHAT comes here?
    // Throw an Exception?
    // What show on screen ?
    }
    }

    Thanks in advance!

  2. […] Sacha Barber on Lookless Controls / Themes […]

  3. sacha says:

    I would not throw an Exception personally, but you could. I think a good way of finding out what to do, is by using Reflector and looking at how the standard controls written by Microsoft work. They actually do nothing in most cases, so the control WILL NOT work correctly, that seems to be the done thing.

  4. Mike Strobel says:

    I have seen mainstream WPF controls that throw exceptions if the required TemplatePart is not present (some Infragistics controls do this). However, Microsoft’s own philosophy seems to be that it’s better to silently ignore the problem and have the control not function properly. This is in line with their approach to data binding failures and other runtime failures in WPF, which Trace the problem and resume gracefully without throwing exceptions. I tend to think this is the best way to go, as long as you are diligent about identifying your TemplateParts with the TemplatePartAttribute. It’s fair to let the template developer figure out why his template is failing, but only if you tell him what parts to include :).

  5. Mike Strobel says:

    As an addendum, does anyone know how to get Visual Studio to display a type or member’s attributes in Object Browser? The only complaint I have with WPF is that a lot of functionality is governed by attributes, but the discovery of those attributes seems to require a tool like Reflector.

  6. sacha says:

    Yeah this is what I would tend to do also.

  7. Pavan says:

    Hello Sacha!

    I need a little help with a medical application that i am developing using WPF. could i email the query to you, please?

  8. sacha says:

    Sadly I do not have the time for this, sorry.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: