Welcome to WindowsClient.net | Sign in | Join

Nidonocu - typeof(Tea)

Random discoveries as I play with WPF

Sponsors





  • advertise here

February 2008 - Posts

WPF - Themes and Control Libraries - Part 1

Anyone familiar with the Windows Presentation Foundation will know you can make a control look like anything you want. This feature can be used to great effect to give your application a unique look and feel. It can also be abused however and make an application stick out like a sore thumb.

To help combat this, you might want to consider using Themes in your application. Themes are a collection of WPF styles that are automatically selected when the user is using a specific Windows theme. You can see themes in action by dropping a Button control on to a Window, running the application and then, for example, changing your system theme from Luna or Aero, to Classic. The button will gain that boxy look of the Windows 2000 era.

 Automatic Themes in action

There are videos and guides out there on how to use themes (this one on WindowsClient.net is a good example and what I used as a starting point) but they all assume that your controls are in the same assembly as your main executable and often in 'real world' applications, this is not the case.

What follows is a step by step guide in creating an application with a custom control in a library, applying different themes and using the control in an application.

 

Getting Started

New Project WindowCreate your solution and choose WPF Application from the list of templates. I am using Visual C# 2008 Express Edition so don't be surprised if your new project window is quite different from the one pictured here.

Next you will want to add the Control Library project to your solution, if like me you are using the 2008 Express Edition, you will notice someone at Microsoft forgot to include this template. Choosing the basic Class Library template instead though can be a pain later when you are trying to add WPF related files and their templates are not in the listed in the Add New Item window.

Instead, add another WPF Application Project to the solution by right-clicking the solution name at the top of the Solution Explorer, pointing to add and then clicking New Project. You may be prompted to save the solution. When you have done that, select WPF Application and give this new 'application' project the name of ControlLibrary or similar.

You must then change the project type to a Class Library. To do this, double click the Properties item in the library project and on the Application tab of the Properties screen, find Output type and select Class Library from the list of options. You should then also delete the App.xaml and Window1.xaml from the library project.

Changing the Output Type

Finally, add a reference to the library in the application project by right-clicking the References item in the application project and clicking 'Add Reference'. Then from the Add Reference window, click the Projects tab and select the Control Library project. Then click OK.

 

Creating the Control

Add New Item Window To create the custom control, right-click the Control Library Project in the Solution Explorer, point to Add and then click Add New Item. From the list of templates, choose Custom Control (WPF) and type a name for the control. In my case, I'm going to make a special button so I've called it CustomButton. Make sure not to chose a User Control or User Control (WPF), these are quite different things all together.

When you click Add, Visual Studio will suddenly do a number of things which you might not be familiar with if this is your first custom control.

  • Firstly it will create a CustomButton.cs file. This is expected, but note it is just a code file, not a XAML file with a code-behind file like the Window1 or App files.
  • It will also create a folder in your project called Themes and in to that place a Resource Dictionary called Generic.xaml. This is where the look and feel of all the custom controls you make is stored. If you haven't used a Resource Dictionary before, think of it as a CSS file for your control. It only stores its appearance, not how it works.
  • Finally, a reference is added to your library project to the .net library UIAutomationProvider. This library lets you add accessibility features to the control and also test the control later if you so wish.

The next step is to think about the custom function we want the control to do. In my case, I want to add a specific text field to my button so it will have a secondary label to what ever we decide to set as the main content for the button. This is a bit of a silly example, but it is simple to make so lets go with it.

Open the CustomButton.cs file if it is not already open and delete the giant helpful XML comment that's been added by the template. Replace it something more concise such as:

/// <summary>
/// Defines a button with a secondary text field
/// </summary>

Next change the type that the CustomButton class inherits from. Change it from Control to Button. This will give us all the basic functions of a standard Button control (like Click events and so on) for free.

public class CustomButton : Button

Finally, we want to provide a place for the button to store the extra text for the secondary label. For that we will use a string stored in a Dependency property so that all changes to the text cause the user interface to update automatically.

///<summary>
/// Gets or sets the secondary text to display.
/// This is backed by a Dependency Property.
///</summary>
public string SecondaryText
{
  get { return (string)GetValue(SecondaryTextProperty); }
  set { SetValue(SecondaryTextProperty, value); }
}
///<summary>
/// This is the backing Dependency Property for the SecondaryText property.
///</summary>
public static readonly DependencyProperty SecondaryTextProperty =
  DependencyProperty.Register("SecondaryText", typeof(string),
  typeof(CustomButton), new UIPropertyMetadata(default(string)));

I won't go in to detail about Dependency Properties since there are plenty of things out there that can explain them better. However, I have also included a Code Snippet file at the end of this entry that you can use to quickly create these from a code template.

There! We've made the control! That was easy.

Of course right now if we tried to use this control, we wouldn't be able to see anything so let's create a basic look for our control.

 

Create a Generic Style

To start working on a basic style for the control, open the Generic.xaml file located in the Themes folder of the ControlLibrary project.

You can see that a basic style has been created for you already:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Nox.Blog.ControlLibrary">
 
 
  <Style TargetType="{x:Type local:CustomButton}">
   <Setter Property="Template">
    <Setter.Value>
     <ControlTemplate TargetType="{x:Type local:CustomButton}">
      <Border Background="{TemplateBinding Background}"
              BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}">
      </Border>
     </ControlTemplate>
    </Setter.Value>
   </Setter>
  </Style>
</ResourceDictionary>

Again, its important to remember that this file is shared for all the custom controls that you make in this library so as you add more custom controls, this file will grow.

Lets make some changes to the template this style uses so that the control displays the content and secondary text values for the control in a simple box.

Change the Border element inside the ControlTemplate so that it reads as follows to add a visible box.

<Border BorderBrush="Black"
        BorderThickness="1">

Next, we want to add a Grid to layout the contents of our control, a ContentPresenter to show the Content Property and a TextBlock for the SecondaryText Property. Start by adding the following inside the Border element:

 

<Grid>
 <Grid.RowDefinitions>
  <RowDefinition Height="0.5*"/>
  <RowDefinition Height="0.5*"/>
 </Grid.RowDefinitions>
 
</Grid>

This defines a Grid with two rows which have half the available space each. Following on from that, add a ContentPresenter and TextBlock inside the Grid with the following attributes:

<ContentPresenter HorizontalAlignment="Center" Grid.Row="0"/>
<TextBlock HorizontalAlignment="Center" Grid.Row="1"
           Text="{TemplateBinding SecondaryText}"/>

The first two attributes of each element are fairly clear. ContentPresenter automatically gets its content from the Content property of the control so requires no further attributes to work correctly. TextBlock though needs a template binding to the SecondaryText property we created in order to know what to display. Template binding to a Property you create is no different from binding to a built in one.

Not very exciting but it will do for now, lets actually see what that looks like.

Using the control

If you read the 'giant helpful XML comment' before you deleted it, you'll already know this bit, but if you didn't, here's how to use the control in the application half of your solution.

First, open the Window1.xaml file in the application project. I myself renamed this to MainWindow.xaml so watch out for that in the rest of the code I post.

If you also rename the Window, remember to update the references to it in the x:Class attribute, the names in the code-behind file and the reference to it in the App.xaml file.

MainWindow by default looks a little something like this:

<Window x:Class="Nox.Blog.ThemedApplication.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
</Window>

If you look at the top, you can see that the file has already defined two namespaces (using the xmlns attribute). The default one for all the built in WPF elements and one named x for other important framework items. We must add our own namespace for the custom control library so that we can reference the items in that. To do that, change the Window element to the following while taking in to account the names you gave the ControlLibrary in your solution.

<Window x:Class="Nox.Blog.ThemedApplication.MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:custom="clr-namespace:Nox.Blog.ControlLibrary;assembly=Nox.Blog.ControlLibrary"
 Title="Themed Application" Height="300" Width="300">

If we break down this line, we can see the following:

  • xmlns:custom - This says we are creating a new namespace and that it will be using the name 'custom'.
  • clr-namespace:Nox.Blog.ControlLibrary - This indicates the contents of the namespace come from a code namespace in our application, this is similar to a 'using' statement at the top of a C# code file. If we were accessing a code namespace in the application assembly, we could stop here.
  • ;assembly=Nox.Blog.ControlLibrary - Since we are not accessing a code namespace inside the application assembly (the EXE), we need to give the name of the DLL file that the control resides in. In this case, its the same as the code namespace, however it could be different. For example, you might compile the library to just CustCtrls.dll in which case, this section would read ;assembly=CustCtrls instead.

I also took a moment to change the Windows's Title attribute to Themed Application for presentation. :)

Finally, the last line. Place this directly inside the Window element:

<custom:CustomButton HorizontalAlignment="Center"
                     VerticalAlignment="Center"
                     Content="Hello"
                     SecondaryText="WindowsClient.net"/>

Let's break this down. First it starts with the custom: namespace identifier that we set in the last step. Then the name of the control, CustomButton. Then we set the Horizontal and Vertical Alignment. These along with the Content attribute are defined by Button and its parent types which we inherited from. Finally we set the SecondaryText attribute which again, doesn't need any special treatment and can be used as easily as the built in properties.

If you want, you can also set the Content by doing something silly like this:

<custom:CustomButton HorizontalAlignment="Center"
                         VerticalAlignment="Center"
                         SecondaryText="WindowsClient.net">
      <CheckBox/>
</custom:CustomButton>

SecondaryText however must be a string since that's what we defined it as in the code file.

If all goes well, pressing F5 in Visual Studio to Build and Run should give you something like this:

 The Generic style displayed

Now at this point, if you had no intension of making the control themeable, we could stop here. Just continue to edit the Generic.xaml until we have a really nice looking control. Instead, in the next part, we'll have a quick look at how the theme system works and then dive in to it.

Notes: I originally was going to make this first post contain both the custom control creation and theme parts, however, this has turned already in to quite a monster so breaking it up seemed to make sense. Please leave a comment if you found this useful or if you think I could improve on any parts of it!

Themed Application Solution and Code Snippet (Part One)

Zip File - 25KB
One last try

Okay, so I think I've tried to start a coding and programming blog at least twice already now and I realised that just talking about what I'm working on wasn't interesting. The thing that made other blogs interesting was the useful content they provided on how their authors solved problems in what ever it was they were working on.

So with that in mind, let me make this one of few non-educational posts and just quickly give a quick set of blog meta data for future reference by those curious.

Who?

I'm Nidonocu, or James to less interesting people. I was born and still exist in the UK. Now living specifically in Sheffield where I moved to attend Sheffield Hallam University for a networking foundation degree. While working on that, Microsoft released the Express editions of Visual Studio and from there I quickly found learning to write software in that to be a nice distraction from an informative but sometimes dull university course. ;)

Outside of coding I also enjoy playing Halo 3 and other titles on my Xbox 360 and Wii, writing the (or should that be an?) odd novel and drawing things.

What?

I started with Visual Basic.net, then moved on quickly to C# and Windows Forms. I'm now working with C# 3.0, .net 3.5 and of course the Windows Presentation Foundation. I've been working on a long term project of a MU* client which through the process of making it I've learnt a lot about the platform and software development and design in general. I'm also now building a kiosk style application for a convention with some interesting attributes and a deadline so that will likely grab most of my focus for the next couple of months.

When?

I hope I can post something interesting at least once a week. Far better to have something interesting and regular rather than lots of boring useless posts.

 

Over the next few days I'll look in to styling this blog and adding other things like a blogroll and that. For now I'll just say thanks to the WindowsClient.net team for the blog space and to Rob Relyea for mentioning this new space to write in.

Posted: Feb 12 2008, 01:27 PM by Nidonocu | with no comments
Filed under: ,
Page view counter