I’m a big fan of building controls. I love writing them, designing them, trying to make it work in as many scenarios I can while keeping them simple, extensible and most importantly reusable. In fact for the past 6 years, it’s all I’ve been doing full time (first ASP.NET and later XAML), and frequently in my spare time as well.
If you dabble in XAML, you have most likely already been building some controls, by going “File -> New -> User Control” in Visual Studio. You probably do this because you want to create a new page in your app, or you just want to encapsulate some of the UI in a separate section. Or perhaps it’s because you realize that this little tidbit can be used over and over again in your application. Or maybe you have even considered it can be used again and again across many DIFFERENT applications. If you have tried those two last categories (or if you will one day), this blog post is for you! It will apply to any of the XAML techs there is: WPF, Silverlight, Windows Phone and the future Windows 8 Runtime.
Despite the title in this blog post, User Controls are awesome. They are quick to throw together and reuse over and over - and there’s a lot of value in that. But what if I told you there’s another control type that has even MORE power, better performance, and can be way more flexible and reusable than a user control, and where the clean code will make most developers fall in love?
The thing is you already know this because you’ve been using them all the time: Button, ListBox, ItemsControls, Grid, StackPanel etc. are all controls harnessing the same power you can! And you have probably seen XAML styles that completely changed the look and feel of a control, without touching any of its code. To give you an idea of how powerful this is, look at this Silverlight Sample below. On the left you will see a ListBox binding to a list of planets. You have probably already done something like this. On the right, you see a solar system. But in fact this is ALSO a ListBox. And there is NO extra code involved here. It’s done entirely by restyling the template. Notice how selection and up/down keys work just like it does with the “normal” ListBox. So I got to reuse all the code that has this, and all I had to do was restyle the ListBox a bit. Something I could have done entirely in Blend without ever touching code.
Let me repeat that: I didn’t add any code to the ListBox do this. In fact the code behind for this page is completely empty. If you don’t believe me, here’s the source code. You can also see more about this technique in this presentation from Mix’08, or read David Ansons blogpost on it.
So at this point hopefully I have won you over to learning more about Custom Controls (if not I’m amazed you have read this far :-).
The Anatomy of A User Control
To start, let’s first look at the anatomy of a typical UserControl and try and fully understand how that works first. Below here we have the XAML portion of our control that defines the layout. We’ll keep it simple and have a Grid with a Button inside it:
<UserControl x:Class="MyApp.SilverlightControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Click Me" Click="Button_Click" Opacity=".5" />
</Grid>
</UserControl>
And we have the code-behind that loads up the control, handles user interaction etc.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SolarSystemRetemplate
{
public partial class SilverlightControl1 : UserControl
{
public SilverlightControl1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LayoutRoot.Background = new SolidColorBrush(Colors.Red);
}
}
}
The things worth noting here are two things: “LayoutRoot” is defined in the XAML using x:Name, and we automatically get a variable by that name in code behind. Also the event handler hooked to the Button’s Click event is magically linked to the code-behind. All this is really handled by the compiler and the “InitializeComponent” call - a method that interestingly doesn’t exist here. The reason this works is really because this is a partial class as indicated, and Visual Studio creates a little ‘secret’ file under the covers for you. You can get to if if you right-click the method and select “Go To Definition”. Here’s what the contents of that file looks like:
namespace MyApp {
public partial class SilverlightControl1 : System.Windows.Controls.UserControl {
internal System.Windows.Controls.Grid LayoutRoot;
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
if (_contentLoaded)
return;
_contentLoaded = true;
System.Windows.Application.LoadComponent(this,
new System.Uri("/MyApp;component/SilverlightControl1.xaml",
System.UriKind.Relative));
this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
}
}
}
You’ll notice that the LayoutRoot is defined here as internal, and it’s assigned using the “FindName” method.
This is one of the nice things about UserControls: A lot of the work is automatically done for you, but with Custom Controls you will have to do this yourself! (but this isn’t so bad considering the power you get!). And here’s the kicker: A UserControl is just another custom control!
The Anatomy of A Custom Control
A custom control doesn’t have a XAML and a code-behind component in the same way UserControl does. Instead it’s ALL code along with a default XAML template. You can consider the template the equivalent of the XAML in the User Control, but the important part to remember here is that this template can be changed by ANYONE, which is what I did to the ListBox in the solar system sample. Another thing to note is that since the template doesn’t have a corresponding code-behind where Visual Studio generates a partial class for you, any event handlers cannot be defined in the template. So how do we go about recreating the user control above as a custom control?
For Silverlight this is easy. Right-click your project and select “File -> Add New -> Silverlight Templated Control”. WPF and Windows Phone doesn’t come with this template so you’ll have to do it manually there, by creating a class and a generic template file. After you do this, you’ll notice two new files: First a simple C# class, and second a new file in \Themes\Generic.xaml. The second file is where you place all templates for all your controls in that assembly. It HAS to have this name and live in this folder for the custom control to pick up the template.
Below is what this template looks like. I’ve added the grid and the button inside the suggested border that was created for me.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp">
<Style TargetType="local:TemplatedControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TemplatedControl1">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="LayoutRoot">
<Button x:Name="ClickButton" Content="Click me!" Opacity=".5" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
First notice the TemplateBinding statements on the border. This is an important feature of controls. You can bind straight to a dependency property defined in your control code. Since a custom control inherits from “Control”, you automatically get Background, BorderBrush, BorderThickness and many other general properties from the inheritance. The great thing is that you can just write <my:TemplatedControl Border=”Red” /> and the border will automatically be bound into this template (and anywhere else where you have a TemplateBinding to that property). This beats UserControl, where to accomplish this in Silverlight you will have resolve to a hack by setting the DataContext of the control to itself, breaking the DataContext flow.
Second, notice that I didn’t add a click-handler to Button. If I did, this template would fail to load. We’ll hook the click handler up later.
Next let’s look at the code for the control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace MyApp
{
[TemplatePart(Name="LayoutRoot", Type=typeof(Control))]
[TemplatePart(Name = "ClickButton", Type = typeof(ButtonBase))]
public class TemplatedControl1 : Control
{
Control layoutRoot;
ButtonBase button;
public TemplatedControl1()
{
this.DefaultStyleKey = typeof(TemplatedControl1);
}
public override void OnApplyTemplate()
{
if (button != null) //unhook from previous template part
{
button.Click -= new RoutedEventHandler(button_Click);
}
button = GetTemplateChild("ClickButton") as ButtonBase;
if (button != null)
{
button.Click += new RoutedEventHandler(button_Click);
}
layoutRoot = GetTemplateChild("LayoutRoot") as Panel;
base.OnApplyTemplate();
}
private void button_Click(object sender, RoutedEventArgs e)
{
layoutRoot.Background = new SolidColorBrush(Colors.Red);
}
}
}
First I declare the “TemplatePart” attributes on the control. They tell what parts (ie controls) are expected to be in my template. In this case LayoutRoot of type Panel (Grid is a Control), and ClickButton of type ButtonBase. These are not strictly required, but they help Expression Blend understand the template requirements when you later customize the control. I always declare the lowest needed type in the control hierarchy to make the template more flexible. For instance I use ButtonBase and not Button, because I only rely on the Click event which is declared on the ButtonBase base class. That way I don’t lock a user of the control into using “Button” but they can place ANY control that inherits from ButtonBase here. Same thing applies for the LayoutRoot, where I just need the Background property.
Next the control inherits from “Control”. Custom controls must inherit from this.
In the constructor I define the “DefaultStyleKey”. This tells the framework that I have a default template defined in Themes\Generic.xaml. If I didn’t the user would always have to explicitly defined a control template for the control.
Lastly, the most important part is “OnApplyTemplate”. This method is called when the control has loaded the template. This is our earliest opportunity to grab references to controls in the template, ie. the TemplateParts. In this case I grab a reference to the ButtonBase defined in the template. If it’s found, I’ll add a click handler to it. Also if a new template gets applied, I must remember to unhook from the previous instance (this is a rare scenario though, and you could probably get away with skipping that bit). It’s also important to note that Template Parts are always optional! So always do the null check anywhere you rely on a reference to a template part.
And that’s really it! I kept the sample simple, so it is easier to go through the individual parts of a control, therefore the differences between a custom control and a user control doesn’t really stand out. If this was all you needed to do, a custom control is probably overkill. But think of scenarios where you have a lot of code-behind that you want to reuse, but you don’t want to lock the design in. The major next parts you will want to add to this now is more Dependency Properties you can bind into the template, as well as VisualStates - ie. storyboards that triggers on certain events. The great thing about Visual States is that the code-behind doesn’t define the storyboard or what it does - only when it starts. This gives the user even more flexibility to customize the behavior.
Adding Visual States to the control
Let’s add some mouse over states to our control, and have the control animate when that happens. In the code-behind where we defined the TemplateParts let’s add two TemplateVisualState attributes:
[TemplateVisualState(GroupName = "HoverStates", Name = "MouseOver")]
[TemplateVisualState(GroupName = "HoverStates", Name = "Normal")]
Again these are optional, but great for Blend integration.
Next add the code that triggers the visual state to the control:
bool isMouseOver;
protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
{
isMouseOver = true;
ChangeVisualState(true);
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
{
isMouseOver = false;
ChangeVisualState(true);
base.OnMouseLeave(e);
}
private void ChangeVisualState(bool useTransitions)
{
if (isMouseOver)
{
GoToState(useTransitions, "MouseOver");
}
else
{
GoToState(useTransitions, "Normal");
}
}
private bool GoToState(bool useTransitions, string stateName)
{
return VisualStateManager.GoToState(this, stateName, useTransitions);
}
This is really all the code we need. It’s pretty simple. If the mouse is over, trigger the MouseOver state, else trigger the Normal state. Note how we don’t really define what “MouseOver” looks like. That’s the job of the template. Let’s define that (you might already be very familiar with this when overriding templates - it’s exactly the same thing, except we get to define the default state):
<ControlTemplate TargetType="local:TemplatedControl1">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoverStates">
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="BackgroundElement"
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
To="Yellow" Duration="0:0:.5" />
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="BackgroundElement"
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
To="Transparent" Duration="0:0:.5" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="LayoutRoot">
<Rectangle x:Name="BackgroundElement" Fill="Transparent" />
<Button x:Name="ClickButton"
Content="Click me!" Opacity=".5" />
</Grid>
</Border>
</ControlTemplate>
So the changes here is adding a rectangle in the background that we animate into yellow when the mouse hovers over.
You now have a control that sets a background on some Panel when some ButtonBase is clicked, as well as running an animation on MouseEnter/Leave. This could serve the purpose for quite a lot of controls, without you having to rewrite the code!
Here’s a few resources you will want to read if you want to learn more about this:
A couple of other controls I’ve built over the time and described on this blog:
If you want to go even more hardcore, wrap your head around the ArrangeOverride and MeasureOverride methods. This is where you can get some really amazing control over how the contents are laid out, but this is outside the scope of this article, but I urge you to read into it. Here’s one article to get your started on that: http://www.switchonthecode.com/tutorials/wpf-tutorial-creating-a-custom-panel-control