Building a custom control using XAML and C#

You may already know that one of the most powerful features of the Windows 8 XAML platform is the flexibility the platform provides to create custom controls. XAML provides features like Dependency Properties and Control Templates that make it easy to create feature-rich and customizable controls.

In the last post, “Building a custom control using the Windows Library for JavaScript ,” Jordan Matthiesen walked you through creating a custom HelloWorld control. In this post I walk you through creating that same control in Xaml. I introduce techniques and concepts that allow you to create reusable custom controls and show how to create template to style these controls. I introduce concepts like Dependency Properties and using a custom Generic.xaml file to create an implicit style that defines a default control template.

A simple XAML control

First, we build the Hello World of controls: a class that derives from Windows.UI.XAML.Controls.Control. Create a new project in Visual Studio using the Blank Project template. Name your project CustomControls. Add your custom control using the New Item template wizard.

Visual Studio includes an item template for a templated control. Right-click the project and select Add -> New Item

Visual Studio includes an item template for a templated control. Right-click the project and select Add -> New Item”

The templated control item template creates the files and some skeleton code to get your custom control started

Select the Templated Control item and name the control “HelloWorld.cs”

This template creates this class:

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }
}

In this short block of code, we specified two very important details. First is that the HelloWorld class derives from Control. Second, setting DefaultStyleKey indicates to the XAML platform that this class uses an implicit style. The templated control template also adds a folder named Themes, and in that folder creates a new file named Generic.xaml. There’s more on this template in the Project templates.

A control’s default style is defined in a custom Generic.xaml file that the platform loads automatically. In this file you define an implicit style, meaning that we can define one style that will be applied by default to all controls of a specific type. Add the highlighted XAML to new Generic.xaml file in the Themes folder:

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/XAML/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/XAML"
    xmlns:local="using:CustomControls">

    <Style TargetType="local:HelloWorld">
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:HelloWorld">
                    <Border
                       Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <TextBlock Text="HelloWorld" 
                               FontFamily="Segoe UI Light"
                               FontSize="36"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style> 
</ResourceDictionary>

These few lines of XAML define a style that will be applied by default to all instances of the HelloWorld control. We define a Control Template, and this template specifies that this control is simply a TextBlock with the Text “HelloWorld”.

Now go to you MainPage.xaml file and add the next markup. You must include the “local:” designation to let XAML know in which namespace to find the HelloWorld class. The “local:” designation is defined at the top of the XAML file.

 <local:HelloWorld />

When you run your app, you’ll see that the control has been loaded and it displays the text “Hello, World!”

Defining control options

Controls become more useful and reusable when we add configurable options. Let’s add an option to allow the control to be set to blink.

To add this option, we add a Dependency Property (DP) to the control. You can learn a lot more about DPs in Dependency properties overview. There is a Visual Studio snippet that makes adding DPs really easy. With the cursor below the constructor, type “propdp” and press Tab twice. You can fill in the specs by tabbing through each parameter in the snippet.

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }

    public bool Blink
    {
        get { return (bool)GetValue(BlinkProperty); }
        set { SetValue(BlinkProperty, value); }
    }

    // Using a DependencyProperty enables animation, styling, binding, etc.
    public static readonly DependencyProperty BlinkProperty =
        DependencyProperty.Register(
            "Blink",                  // The name of the DependencyProperty
            typeof(bool),             // The type of the DependencyProperty
            typeof(HelloWorld),       // The type of the owner of the DependencyProperty
            new PropertyMetadata(     // OnBlinkChanged will be called when Blink changes
                false,                // The default value of the DependencyProperty
                new PropertyChangedCallback(OnBlinkChanged)
            )
        );

    private DispatcherTimer __timer = null;
    private DispatcherTimer _timer
    {
        get
        {
            if (__timer == null)
            {
                __timer = new DispatcherTimer();
                __timer.Interval = new TimeSpan(0,0,0,0,500); // 500 ms interval
                __timer.Tick += __timer_Tick;
            }

            return __timer;
        }
    }

    private static void OnBlinkChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e
    )
    {
        var instance = d as HelloWorld;
        if (instance != null)
        {
            if (instance._timer.IsEnabled != instance.Blink)
            {
                if (instance.Blink)
                {
                    instance._timer.Start();
                }
                else
                {
                    instance._timer.Stop();
                }
            }
        }
    }

    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
    }
}

Back in MainPage.xaml, add the configuration option to the control.

 <local:HelloWorld Blink="True" />

Run the app and watch the control blink away!

Adding support for events

By adding events to controls you can increase functionality. Events allow you to get interrupts from the control when actions take place, and then you can run code that reacts to the actions. Let’s add an event that fires each time the control blinks.

 public class HelloWorld : Control
{
    
    ...
...
    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    public event EventHandler Blinked;

    private void OnBlinked()
    {
        EventHandler eh = Blinked;
        if (eh != null)
        {
            eh(this, new EventArgs());
        }
    }
}

Back in MainPage.xaml, add an x:Name property to the element so that we can retrieve the control instance later in code:

 <local:HelloWorld x:Name="HelloWorldWithEvents" Blink="True" />

Now in MainPage.xaml.cs, add an event listener function delegate to print debug output when the event is fired.

 HelloWorldWithEvents.Blinked += (object sender, EventArgs args) =>
{
    System.Diagnostics.Debug.WriteLine("HelloWorldWithEvents Blinked");
};

When you run this code, you see a message written out every 500 milliseconds to the Output Console window in Visual Studio.

Exposing public methods

We’ve already written a private method DoBlink to cause the control to blink, and we now make this method public to allow you the option to blink the control at any time. All you need to do is change the visibility of the DoBlink method to public:

 public class HelloWorld : Control
{
    ...

    public void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    ...
}

Now you can call the public DoBlink method in C# code-behind, perhaps in a button click handler.

 private void Button_Click_1(object sender, RoutedEventArgs e)
{
    HelloWorldWithEvents.DoBlink();
}    

Reusing controls

When you only want to use a control in a single project or for your own personal use, there isn’t much to think about in terms of packaging and distribution. But if you want to build a reusable control to share with other developers, you have some options. One option is to create a Visual Studio Extension SDK that other developers can install on their machines and add to their projects. For more info on how to do this, check out Creating an SDK using C# or Visual Basic.

Bringing it all together

We’ve just worked through the most common aspects of a control that you’ll want to implement:

  1. Getting your control onto a page
  2. Passing in configuration options
  3. Dispatching and responding to events
  4. Exposing functionality via public methods

So there you have it, the basics of XAML custom controls. The next step is to find some functionality you want to add to an existing XAML control and either change the style or subclass the control to add your own functionality. As I became more comfortable with custom controls, I felt so much more confident writing XAML apps. I hope you will experiment with your own custom controls and post your experiences online.

I hope you’ve found this post useful! If you have questions while working on your own controls, head over to the Windows Dev Center and ask questions in the forums.

--Aaron Wroblewski

Program Manager, Windows