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

Comments

  • Anonymous
    October 15, 2012
    Hola, muy buena aportación. La verdad es que queda muy claro cada uno de los pasos a seguir. Y lo mejor de todo, hara parpadear cualquier control que uno quiera. Muchas gracias, Miguel

  • Anonymous
    October 15, 2012
    I'm not quite sure if this applies to WinRT also, but the shown event handler function delegate HelloWorldWithEvents.Blinked += (object sender, EventArgs args) => {    System.Diagnostics.Debug.WriteLine("HelloWorldWithEvents Blinked"); }; reminds me of a .NET memory leaking "lambda event handler" - see stackoverflow.com/.../16484. If so, I always ask myself if it is useful to put this memory leaking practice in the mind of the reader...? Or am I missing something here? What do you think?

  • Anonymous
    October 15, 2012
    Thank You, Thank You, Thank You.  Great post.  This is the best description of custom controls I've read.  I learned a ton here.  Everything from Generic.xaml to a fairly good primer on Dependency Properties....and even a bit on events and exposing methods.  Cool.  FYI....I wrote a nice comment for this post last night on my Win8 machine.....said "Publishing"....but obvioulsy did not.

  • Anonymous
    October 17, 2012
    I've uploaded a sample implementing the code in this post to the Windows 8 Dev Center. Check it out here: bit.ly/XAMLHelloWorldCustomControl Let me know if you have any questions! Aaron

  • Anonymous
    October 17, 2012
    Tom's right, hooking the function delegate to the event handler in OnNavigatedTo means that a new instance of the delegate will be added each time. I've updated the sample project to show another way to this that will not leak memory. bit.ly/XAMLHelloWorldCustomControl       protected override void OnNavigatedTo(NavigationEventArgs e)       {           HelloWorldWithEvents.Blinked += HelloWorldWithEvents_Blinked;       }       protected override void OnNavigatedFrom(NavigationEventArgs e)       {           HelloWorldWithEvents.Blinked -= HelloWorldWithEvents_Blinked;       }       private void HelloWorldWithEvents_Blinked(object sender, EventArgs args)       {           System.Diagnostics.Debug.WriteLine("HelloWorldWithEvents Blinked");       } Thanks Tom!

  • Anonymous
    October 17, 2012
    very nice.  we might be able to develop our own custom live tiles with this code sample. just one question as I'm just learning XAML.  can we dynamically create another custom XAML control inside a custom XAML control?

  • Anonymous
    October 18, 2012
    I followed your sample, but I get the following error: The name "MyControl does not exist in the namespace "using:app3". I can't figure out what I'm missing.

  • Anonymous
    October 18, 2012
    Never mind... I closed and reopened the solution and now it's working. Go figure...

  • Anonymous
    October 18, 2012
    Nice blog here! Also your website loads up fast! What host are you using? I wish my website loaded up as fast as yours lol !

  • Anonymous
    October 19, 2012
    In your example, how would I change the Text property of the TextBlock inside my custom object? How do I expose it or make it public? Do I create a DependencyProperty? And if so, How do I relate it back to the TextBlock inside the template?

  • Anonymous
    October 22, 2012
    OK, I figured it out. Actually, I changed the TextBlock to a Button, which worked better for what I wanted. Then, I used <Button Content="{TemplateBinding Content}" /> and now I can edit the Content in code. The next problem I'm trying to solve is that when I press the button, the Foreground text changes to Black. How do I override this behaviour? I've tried using a ColorAnimation VisualTransition in Blend, but when I run the app, I still get black text.

  • Anonymous
    October 26, 2012
    Hi Aaron, on how to package controls, you mention that "One option is to create a Visual Studio Extension SDK that other developers can install on their machines and add to their projects. " is that to imply there is any other (hopefully cleaner) solution? We've been looking for a solution on how to cleanly package custom controls that will be easy for 3rd party developers to use.