Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

Reusing Control Templates in Resource Dictionaries

Rate me:
Please Sign up or sign in to vote.
4.98/5 (24 votes)
8 Apr 2010CPOL8 min read 85.5K   3K   33   15
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:

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

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

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

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

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

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

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

XML
<Border MouseLeftButtonDown="titleBar_MouseLeftButtonDown"...

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

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

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

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

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


Written By
Software Developer
South Africa South Africa
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionUnable to make it work Pin
Member 1026761719-Sep-13 2:21
Member 1026761719-Sep-13 2:21 
I downloaded and installed the nugget, but my project wont add a reference to the MetroApp. I'm really new to .xaml coding, but I copy-pasted the the snippet from your website where I will use ALL the metro-app's styles.
But as I said, my solution seems to be unable to find a referance to the actual .xaml, eventhough (I suppose) the link in the source="pack://application:,,,/Selen.Wpf.SystemStyles;component/Styles.xaml" should be right.

Best regards,
Joakim
GeneralMy vote of 5 Pin
rama charan5-Dec-12 1:44
rama charan5-Dec-12 1:44 
QuestionTitle Bar Buttons... Pin
prakash667-Nov-12 1:32
prakash667-Nov-12 1:32 
AnswerRe: Title Bar Buttons... Pin
Wayne Delport5-Dec-12 18:51
Wayne Delport5-Dec-12 18:51 
Questionmy vote of 5 Pin
bahman aminipour19-Jun-12 0:49
bahman aminipour19-Jun-12 0:49 
QuestionUsing this for projects outside of the same solution as Demo.Common Pin
Dave12022-Mar-12 8:13
Dave12022-Mar-12 8:13 
GeneralNice Pin
IFDSUser15-Mar-12 3:43
IFDSUser15-Mar-12 3:43 
QuestionResize form Pin
fafa6913-Mar-12 6:38
fafa6913-Mar-12 6:38 
QuestionDownload file error Pin
jaromirsulman13-Dec-11 12:46
jaromirsulman13-Dec-11 12:46 
GeneralResize Pin
Jonathan Schmidt2-Oct-10 7:10
Jonathan Schmidt2-Oct-10 7:10 
GeneralRe: Resize Pin
Wayne Delport30-Nov-10 21:29
Wayne Delport30-Nov-10 21:29 
GeneralTruly interesting!!! Pin
CreF8-Apr-10 4:05
professionalCreF8-Apr-10 4:05 
GeneralRe: Truly interesting!!! Pin
Wayne Delport8-Apr-10 4:34
Wayne Delport8-Apr-10 4:34 
GeneralRe: Truly interesting!!! Pin
CreF8-Apr-10 4:38
professionalCreF8-Apr-10 4:38 
GeneralRe: Truly interesting!!! Pin
Wayne Delport8-Apr-10 6:41
Wayne Delport8-Apr-10 6:41 

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.