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

Reusing Control Templates in Resource Dictionaries

0.00/5 (No votes)
8 Apr 2010 1  
Defining control templates in a ResourceDictionary for clean and easy reuse

Introduction

This article and associated demo application will hopefully show the benefits of reusing control templates throughout an application that are defined in ResourceDictionary objects.

Background

Recently I wrote a Windows application that required an Apple Mac OSX look and feel to it. Thanks to WPF and control templates, this was not too much of a problem.

MacStyledWindow.png

What I discovered in the process of developing this application was how Resource Dictionaries alleviated me from having to write repetitive code and mark up. Using the ResourceDictionary class, I was able to define my control template, event handlers, and other related resources in one place (in a separate assembly) and simply point the application's numerous windows' Style properties to the Apple Mac-styled control template.

Defining the Control Template

In my experience, I have come to realize that reusable content should be defined as a separate component (well, duh!). As is with this demo application, the reusable Window control template has been defined in a seperate assembly, along with the resource dictionaries. This makes it easier for other developers to obtain and use the templates with minimal effort.

I need to mention here that I wrote the external assembly as a normal, bare-bones .NET class library. If you are going to follow the same route, one thing to note is you're going to have to add a few references to the project in order to work with resource dictionaries and other windows-type resources. The required references are:

  • PresentationCore
  • PresentationFramework
  • System.Xaml
  • WindowsBase

Ok, moving along. The Window template has been defined in a ResourceDictionary named MacStyledWindow.xaml as follows:

<!-- Window Template -->
<ControlTemplate x:Key="MacWindowTemplate" TargetType="{x:Type Window}">
  <Grid>
    ..
    <!-- The window content. -->
    <Border Grid.Row="1">
      <AdornerDecorator>
        <ContentPresenter />
      </AdornerDecorator>
    </Border>
    ..
  </Grid>
</ControlTemplate>

The majority of the template's markup has been omitted above. What I wanted you to note is the AdornerDecorator and ContentPresenter tags. When you specify that a window in your application should use this window control template, these two tags are what make it possible for you to plug any custom window content (text blocks, buttons, menus, layout controls, etc.) into your windows.

In the same resource dictionary, I have created a Style block, which I use to set a few common Window properties that will allow me to template the window with a Mac look. You will notice that one of the properties I set is the Template property. This points to the Mac window template mentioned earlier.

<!-- Mac Window Style -->
<Style x:Key="MacWindowStyle" TargetType="Window">
  <Setter Property="Background" Value="Transparent" />
  <Setter Property="WindowStyle" Value="None" />
  <Setter Property="AllowsTransparency" Value="True" />
  <Setter Property="Opacity" Value="0.95" />
  <Setter Property="Template" Value="{StaticResource MacWindowTemplate}" />
</Style>

In addition to the MacStyledWindow resource dictionary, the Demo.Common project also contains two other resource dictionaries; one for button styles (MacStyledTitleBarButtons.xaml) and one for gradient brushes (MacStyledButtonBrushes.xaml). I won't cover these in depth. What I would like to point out is how to reference these resource dictionaries from another resource dictionary in the same project.

Near the top of the MacStyledWindow.xaml file, you will see the following markup:

<ResourceDictionary.MergedDictionaries>
  <!-- Resource Dictionary containing Buttons used for the Mac Window titlebar buttons-->
  <ResourceDictionary Source="MacStyledTitleBarButtons.xaml" />
</ResourceDictionary.MergedDictionaries>

You use a MergedDictionaries collection to add a resource dictionary reference. The markup above is an example of how to reference a resource dictionary in the same project. Later, I will show you how to reference a resource dictionary from an external project.

Creating Code-behind for a ResourceDictionary

So, we've defined a control template and a style with a few default properties that enable us to skin our window with a Mac look. This is great to a point; It gives us the ability to give our windows that Mac look, but at the same time we've removed some basic window functionality when applying the template; the user can no longer drag the window around the screen, nor do the pretty Close, Minimize, and Maximize buttons in the title bar do anything when clicked!

TitleBarButtons.png

By default, when you create a ResourceDictionary in Visual Studio, the wizard simply creates a single XAML file for you to contain your resource definitions. Usually this is all you need, but in our case we need to attach a few event handlers to make the templated window more interactive.

Event handlers most commonly live in code-behind files (I think...); classes that link up a UI control event to an event handler of some sort. It therefore makes sense to follow the same approach here to solve our event-handling problem.

What I did was added a new class to the project containing the resource dictionary that defines the control template, made the class partial, changed the class definition so that it inherits from ResourceDictionary, and named it the same as the resource dictionary, but with a .cs extention. From what I've read, the naming convention is not required, but it seems to be the Microsoft standard of doing things, so, if it ain't broken, don't fix it, right?

So we have a resource dictionary named MacStyledWindow.xaml, and we have a code-behind file named MacStyledWindow.xaml.cs. Sorted? Not quite yet. The new code-behind file needs to be linked to the resource dictionary. This is done by specifying the code-behind class in the resource dictionary, using the x:Class="..." syntax:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="Demo_Common.Resource_Dictionaries.MacStyledWindow">

One last thing to do to finalize the setup of our code-behind class is to ensure that InitializeComponent is called when instantiated. I created a constructor for the new class, and placed a call to InitializeComponent in there:

public MacStyledWindow()
{
    InitializeComponent();
}

Adding Event Handlers

Now we have our code-behind setup correctly, we can add the required event handlers. As I mentioned earlier, there is no support for dragging our templated window across the screen, nor do the title bar buttons respond to clicking. This is easily remedied in our new code-behind class.

The window control template I defined has a title bar that is comprised of a Border and a TextBlock:

<!-- The title bar. -->
<Border MouseLeftButtonDown="titleBar_MouseLeftButtonDown" 
	Padding="15" CornerRadius="10, 10, 0, 0">
  <Border.Background>
    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
      <GradientStop Color="#FFABABAB"/>
      <GradientStop Color="#FF202020" Offset="1"/>
    </LinearGradientBrush>
  </Border.Background>
</Border>
<TextBlock Foreground="White" Text="{TemplateBinding Title}"
  MouseLeftButtonDown="titleBar_MouseLeftButtonDown" 
  HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Normal" />

To facilitate the required dragging behaviour, I created an event handler in the code-behind that calls the border and textblock's DragMove() method:

private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var window = (Window)((FrameworkElement)sender).TemplatedParent;
    window.DragMove();
}

I'd come across the DragMove() method previously in an unrelated project I worked on, so that was nothing new. What I did struggle with, however, was how to determine which templated window was raising the MouseLeftButtonDown event. Turns out that was equally as simple. All I had to do was obtain a reference to the FrameworkElement's TemplatedParent, cast the reference to a Window object, and call the DragMove() method on that reference. Voila! All I still needed to do was wire up the border and textblock's event handlers in the resource dictionary:

<Border MouseLeftButtonDown="titleBar_MouseLeftButtonDown"...

I used the same approach for handling click events on the title bar buttons:

// Close button
private void closeButton_Click(object sender, RoutedEventArgs e)
{
    var window = (Window)((FrameworkElement)sender).TemplatedParent;
    window.Close();
}

// Minimize button
private void minimizeButton_Click(object sender, RoutedEventArgs e)
{
    var window = (Window)((FrameworkElement)sender).TemplatedParent;
    window.WindowState = WindowState.Minimized;
}

// Maximize button
private void maximizeButton_Click(object sender, RoutedEventArgs e)
{
    var window = (Window)((FrameworkElement)sender).TemplatedParent;
    // Check the current state of the window. 
    // If the window is currently maximized, return the
    // window to it's normal state when the maximize button is clicked, 
    // otherwise maximize the window.
    if (window.WindowState == WindowState.Maximized)
      window.WindowState = WindowState.Normal;
    else window.WindowState = WindowState.Maximized;
}

Consuming Template from External Project

We have now completed pretty much all the work required on our template, and it is now ready for consumption. There are obviously a number of additional items and functionality not included in the attached template, but the idea here was to show how defining our templates in a single location can reduce the amount of repetitive code and XAML it would otherwise require.

And on that note, it is time for me to show you how our demo application implements the template in all its windows. First off, we add a reference to the assembly containing the template. In this case, it's a project in the same solution, so we simply add a reference to the Demo.Common project.

Next, we add a global reference in the application's App.xaml to the resource dictionary containing the window template. We add the reference here so that the resource dictionary is available throughout the application. If you recall, earlier I showed you how to add a reference to a resource dictionary contained in the same project. Adding a reference to the application's App.xaml, which is in a separate project, is slightly different:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="pack://application:,,,/
	Demo.Common;component/Resource Dictionaries/MacStyledWindow.xaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary> 
</Application.Resources>

The first difference is we need to add a root ResourceDictionary tag. The second difference is the URI format we use when setting the ResourceDictionary's Source attribute. Previously, the resource dictionary was contained in the same project, and so we only had to specify the name of the file containing the resource dictionary. Here, however, the resource dictionary is defined in an external assembly, so we have to use the pack: URI.

<ResourceDictionary Source="pack://application:,,,/Demo.Common;
	component/Resource Dictionaries/MacStyledWindow.xaml" />

What the pack: URI allows us to do is tell the ResourceDictionary where, externally, to find the XAML file. Without going into too much detail, I will briefly explain how the URI works.

The first part of the pack: URI specifies the authority. In the case of our demo application, the resource file is compiled into a referenced assembly, and therefore the authority is set as application:,,,

The second part of the pack: URI specifies the path to the resource file. Here we specify that the AssemblyShortName is Demo.Common, we specify that the assembly being referenced to is referenced from the local assembly by using the ;component keyword, and finally we specify the path to the resource file (including the sub folder name) in the referenced assembly.

With that out of the way, we can apply the template to our windows in the demo application. This is extremely straightforward. All we need to do is set the window's Style property to point to the MacWindowStyle static resource:

<Window x:Class="FortySixApplesResourceDictionaryDemo.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Style="{StaticResource MacWindowStyle}"
>

Conclusion

I found that by defining my control templates and styles in resource dictionaries contained in separate assemblies reduced the amount of repetitive code and XAML I was having to write. There are other benefits to this approach too, of course, such as allowing for parallel development of an application; look and feel separated from business logic, one without affecting the other.

Additionally, one could define multiple "themes" this way, and dynamically load them at run time. If a theme needs to be added or changed, only the assembly containing the control template is affected, and perhaps a key or two in the application's config file...

Using the Code

The demo application is a Visual Studio 2010 solution that contains two projects:

  • 46ApplesResourceDictionaryDemo.csproj
    • This is the main project in the solution. It is a WPF Windows application.
  • Demo.Common.csproj
    • This project is a standard .NET class library, and is referenced by the WPF Window application

Extract the zip archive to your local hard disk. Open the 46ApplesResourceDictionaryDemo.sln solution file. If not already set, make sure the 46ApplesResourceDictionaryDemo project is the startup project.

History

  • 8th April, 2010: Initial post

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