This article describes how to build custom WinRT Metro controls in C# and XAML, with the Developer Preview version of Visual Studio 11. WPF, Silverlight, and WinRT Metro have two ways to roll your own controls: custom controls, and user controls. User controls are composite controls that are created by dragging and dropping other controls onto a XAML surface. They are nothing more than content controls that are optimized for design-time use. User controls are typically for sharing within an application, but not with other applications. Custom controls on the other hand, are created by defining a class that derives -directly or indirectly- from Control, together with a definition of a default style. Custom controls are typically hosted in control libraries, and shared between multiple applications. Custom controls are much more flexible in terms of reuse, styling, templating, and theming.
For this article I created a simplified version of the Slider control. It's called SimpleSlider. It has a Minimum, Maximum, and Value property. Visually it only consists of a moving part -the thumb- and a colored rectangle of which the width is bound to the Value property:
I first created a project in .NET 4.0 WPF with Visual Studio 10. Then I tried to implement the same functionality in a .NET 4.5 Metro-style app. Here are screenshots of both versions:
Creation of the control
For WPF there is a WPF Custom Control Library project template. It creates a custom control derived from the most common base class: Control. You might want to change that into a more appropriate class, like ItemsControl or Selector. RangeBase would have been the ideal base class for a Slider, but I wanted to build everything from scratch, so I kept the default.
The Visual Studio template assigns the DefaultStyleKey dependency property through an OverrideMetaData call in a static constructor. It also creates a Themes folder with a generic.xaml with an (almost) empty default control template for the new user control. The project's assembly information gets a ThemeInfo attribute that contains the locations where that default style will be searched when the control needs to be displayed.
That's how it goes in Silverlight and WPF. For Metro style apps there is NO such project template - there's not even a class template for a custom control. The reason is that things like DefaultStyleKey, ThemeInfo and generic.xaml are not supported (yet?). So you have to create the SimpleSlider class and its generic.xaml manually. To inject the style file into the application, add it as a resource dictionary in app.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Adding Bindable Properties
By deriving from Control, a custom control inherits a set of useful dependency properties. These are properties to which the user of the control can databind: things like Background, FontSize, and Template. You can add custom dependency properties yourself - Visual Studio has a code snippet for that: propdp. It generates the dependency property registration for you:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", "Object", typeof(SimpleSlider).FullName, new PropertyMetadata(0.0, OnValueChanged));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
The PropertyMetadata allows you to define the default value and the method that should be called when the property's value is changed. When the Value property of the SimpleSlider is changed, we're going to redraw it:
private static void OnValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var customSlider = (SimpleSlider)dependencyObject;
customSlider.UpdateControls();
}
Contrary to WPF and Silverlight, the Register method takes strings instead of types. This is just temporary: Microsoft will regain compatibility with Silverlight and WPF in the upcoming beta of WinRT.
In the Developer Preview you need to specify 'Object' as the property type to make the bindings work, but that's also just a temporary bug.
Styling
Defining the default style for your custom control is not different from current technologies. You just define a style with the corresponding target type in a resource dictionary. Here's the full definition of the default style for the simple slider:
<Style TargetType="ctrl:SimpleSlider">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrl:SimpleSlider">
<Grid>
<Border Height="8"
VerticalAlignment="Stretch"
Background="LightGray" />
<Canvas Margin="0"
MinHeight="8">
<Rectangle x:Name="PART_Rectangle"
Height="8"
Fill="Yellow" />
<Thumb x:Name="PART_Thumb"
Width="8"
Height="8" />
</Canvas>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I would suggest to NOT use TemplateBinding in a style definition, since this doesn't seem to work yet. The following code, where I replaced the Border in the template by a Rectangle with its Width bound to the actual width of the control, does not work in the Developer Preview:
<Rectangle Height="8"
Width="{TemplateBinding ActualWidth}"
VerticalAlignment="Stretch"
Fill="LightGray" />
If you're looking for inspiration about styles: all the default styles for WPF and Silverlight controls are on MSDN. You can find the full style for the WPF Slider here. The WinRT Metro styles are not published yet, but if you open the Basic Controls Sample, you'll find a light_generic.xaml file that has all the information. You'll immediately see why my control is called 'Simple' Slider...
Templating
The user of your custom control should be able to decide for 200% on how your control should look like in his application. But of course he needs to respect some basic requirements: a slider will always have a moveable part (the Thumb) and an indication of its Value (the Rectangle). The required elements in a style are by convention named with a 'PART_' prefix. The code behind file also exposes these elements' name and type through a TemplatePart attribute:
[TemplatePart(Name = ThumbPartName, Type = typeof(Thumb))]
[TemplatePart(Name = RectanglePartName, Type = typeof(Rectangle))]
public class SimpleSlider : Control
{
private const string ThumbPartName = "PART_Thumb";
private const string RectanglePartName = "PART_Rectangle";
// ...
}
This indicates to potential control re-stylers that the code-behind relies on these parts. So if you override the default style, you should make sure that it contains elements with the expected name and type.
The style -default or custom- is typically applied in the OnApplyTemplateCore method, that's Metro's version of the OnApplyTemplate method that we know from Silverlight and WPF. As a developer of custom controls, you should wrap the template part manipulation in defensive code. After all, you never know what template you will be dealing with at run-time. Use GetTemplateChild to find the template part you're interested in, since WPF's Template.Find does not exist in Silverlight and Metro:
protected override void OnApplyTemplateCore()
{
base.OnApplyTemplateCore();
this.thumb = this.GetTemplateChild(ThumbPartName) as Thumb;
if (this.thumb != null)
{
this.thumb.DragDelta += this.Thumb_DragDelta;
}
// ...
}
All changes to a dependency property fire the INotifyPropertyChanged event. Unfortunately, in the Developer Preview something goes wrong in the central binding mechanism when the ElementName property is used. The following code displays the initial slider value correctly in a textblock. But when the thumb is moved, the textbox just disappears:
<TextBlock Text="{Binding Path=Value, ElementName=SimpleSlider}" />
If you bring a viewmodel in the equation and use it as datacontext for slider and textbox, then everything works as expected. This looks like overkill, but as a die-hard MVVM developer I can live with that. Here's the -working- code for the value binding of the pacman-shaped slider:
<TextBlock Text="{Binding SliderValue}" />
Overriding a style
To override the default style, you just need to provide a new one, e.g. through XAML:
<ctrl:SimpleSlider.Style>
<Style TargetType="ctrl:SimpleSlider">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrl:SimpleSlider">
<!-- Your template here -->
<!-- ... -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ctrl:SimpleSlider.Style>
Just make sure you use the required template parts.
Conclusion
There are a couple of glitches in the Developer Preview, but in general the WinRT Metro framework already has most of the foundations to build reusable custom control libraries. In a couple of weeks there will be a new release: not a true beta release, but a so-called 'consumer preview'. I can't wait to get my hands on it...
Code
Here's the full code, the WinRT version was built with the Visual Studio 11 Developer Preview:
WPF: U2UConsult.WPF.SimpleSlider.zip (24,81 kb)
WinRT: U2UConsult.WinRT.SimpleSlider.zip (34,84 kb)
Enjoy!
Afterthought
I realize that it was unfair to compare WinRT Metro directly to WPF. The WinRT Metro mechanisms are much closer to Silverlight than they are to WPF. WinRT Metro currently feels like a stripped-down version of Silverlight (which itself is a stripped-down version of WPF). At first, I found that a bit strange. After all, the WinRT runtime is not restrained by footprint size: it doesn't have to be smaller than the Flash plugin. It's also not constrained by browser and operating system compatibility: it doesn't have to run in Opera on a Windows Server 2000, nor in Safari on a Mac. On the other hand, WinRT is targeted to run on cheap (well, hopefully) tablets powered by ARM processors, the 32-bit cores that drive smartphones, MP3-players, and the Nintendo DS. That counts as a constraint.