WPF Custom Controls

Creating a Custom Control

This article was originally published by developmentor
DevelopMentor

Creating a Custom Control

Visual Studio 2008 includes a template for creating WPF custom controls. You may create a control in one of three ways:

  1. Create a library from scratch (select File | New | Project | WPF Custom Control Library)
  2. Create a control library within the context of a solution (from Solution Explorer, right mouse click on the solution node and select Add | New Project | WPF Custom Control Library)
  3. Add a control directly to an existing WPF project (from Solution Explorer, right mouse click on the project node and select Add | New Item | Custom Control (WPF))

This example illustrates adding a custom control library to an existing WPF application, as shown in Figure 1:

Figure 1: Visual Studio 2008 Template for creating WPF custom controls

Visual Studio creates a code behind file for you (in this case—AlarmClockControl.cs), and a boilerplate XAML file that includes a default control layout (it lands in a file named Generic.xaml which appears in the Themes folder of the project).

The Generated Code and XAML

The code generated by Visual Studio is pretty minimal. In the code behind file, you’ll find a class deriving from System.Windows.Controls.Control. There’s a static constructor that sets up the control so that it returns the AlarmClockControl’s type as the DefaultStyleKey property.

public class AlarmClockControl : Control
{
    System.Windows.Threading.DispatcherTimer displayTimer;


    static AlarmClockControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(AlarmClockControl),
            new FrameworkPropertyMetadata(typeof(AlarmClockControl)));
    }
}

The XAML generated by Visual Studio includes a Style targeted to the AlarmClockControl type. The Style sets the control’s Template property to be a simple Border by default. Notice how the Border passes the Background, BorderBrush, and BorderThickness properties are passed through using {TemplateBinding}. We’ll embellish the presentation shortly next.

<Style TargetType="{x:Type local:AlarmClockControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:AlarmClockControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
    

Defining the Presentation

The default ControlTemplate for the control simply defines an empty border. An alarm click should probably show the current time and perhaps a couple of user interface elements for setting the alarm time and for arming the alarm. The following code shows the ControlTemplate with a StackPanel hosting a TextBlock to display the current time, a CheckBox for arming the alarm, and a Button for displaying a dialog box for setting the alarm time.

<Style TargetType="{x:Type local:AlarmClockControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:AlarmClockControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <StackPanel>
                        <TextBlock Foreground="Red" FontSize="36" 
                                   x:Name="PART_CURRENTDATETIME"></TextBlock>
                        <CheckBox x:Name="PART_CHECKBOXALARMSET" >Alarm Set</CheckBox>
                        <Button x:Name="PART_SETALARMBUTTON" >
                            <StackPanel>
                                <TextBlock FontSize="24">Set Alarm Time</TextBlock>
                                <TextBlock FontSize="24" x:Name="PART_SETALARMBUTTONTEXTPANE"
                                           Text="{Binding Source=AlarmClockControl, 
                                                    Path=AlarmTimeSet}">
                                </TextBlock>
                            </StackPanel>
                        </Button>
                    </StackPanel>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Notice that the UI element hosted by the StackPanel have names beginning with the prefix “PART” (that is, “PART_CURRENTDATETIME”, “PART_CHECKBOXALARMSET”, “PART_SETALARMBUTTON”, and “PART_SETALARMBUTTONTEXTPANE”). User interface elements held within custom controls are named this way so that control users that want to swap out the various elements may do so. We’ll see these names again later when we have to access various user interface elements within the control programmatically.

Adding Dependency Properties

Once the control’s presentation is established, it makes sense to expose properties so they may be accessed by the host. Because this is WPF, think about introducing dependency properties so the host client may databind to them. This example defines three dependency properties: the current time, the alarm set time, and a boolean property specifying whether or not the alarm is armed. By making these dependency properties, the client may databind to them directly. For example, if the client would like to have a user interface element that shows the current date and time, the client may bind to the CurrentTime property, which will be changing about once every quarter of a second. As the CurrentTIme property changes, it may be reflected in a control owned by the client.

These are fairly straightforward dependency properties defined and registered in the normal way.

public DateTime AlarmTime
{
    get { return (DateTime)base.GetValue(AlarmTimeProperty); }
    set { base.SetValue(AlarmTimeProperty, value); }
}

public static readonly DependencyProperty
    AlarmSetProperty = DependencyProperty.Register(
            "AlarmSet",
          typeof(bool),
             typeof(AlarmClockControl),
    new FrameworkPropertyMetadata(
       false,
       FrameworkPropertyMetadataOptions.BindsTwoWayByDefault ));

public bool AlarmSet
{
    get { return (bool)base.GetValue(AlarmSetProperty); }
    set { base.SetValue(AlarmSetProperty, value); }
}

public static readonly DependencyProperty
    CurrentTimeProperty = DependencyProperty.Register(
            "CurrentTime",
          typeof(DateTime),
             typeof(AlarmClockControl),
    new FrameworkPropertyMetadata(
       DateTime.Now, null));

public DateTime CurrentTime
{
    get { return (DateTime)base.GetValue(CurrentTimeProperty); }
    set { base.SetValue(CurrentTimeProperty, value); }
}

Adding a Routed Event In addition to some dependency properties, a control usually fires events. In this case, it makes sense for the alarm control to expose a routed event when the alarm goes off. The following code defines the RoutedEvent, declares it as a member of the AlarmClockControl, and implements a helper method named FireAlarm that will raise the AlarmEvent for the control when necessary. We’ll use this helper function in just a bit.

public static readonly RoutedEvent AlarmEvent =
   EventManager.RegisterRoutedEvent("Alarm",
     RoutingStrategy.Bubble, typeof(RoutedEventHandler),
     typeof(AlarmClockControl));

public event RoutedEventHandler Buzz
{
    add { base.AddHandler(AlarmEvent, value); }
    remove { base.RemoveHandler(AlarmEvent, value); }
}

protected void FireAlarm()
{
    base.RaiseEvent(new RoutedEventArgs(AlarmEvent));
}

You might also like...

Comments

About the author

George Shepherd United Kingdom

George Shepherd has been programming Windows since the 2.0 days back in the late '80s. When MFC came out in the early 1990s, George co-authored the definitive reference for MFC, MFC Internals (1...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“In order to understand recursion, one must first understand recursion.”