XAML custom (templated) controls with C++/WinRT

Important

For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

One of the most powerful features of the Universal Windows Platform (UWP) is the flexibility that the user-interface (UI) stack provides to create custom controls based on the XAML Control type. The XAML UI framework provides features such as custom dependency properties and attached properties, and control templates, which make it easy to create feature-rich and customizable controls. This topic walks you through the steps of creating a custom (templated) control with C++/WinRT.

Create a Blank App (BgLabelControlApp)

Begin by creating a new project in Microsoft Visual Studio. Create a Blank App (C++/WinRT) project, and name it BgLabelControlApp. In a later section of this topic, you'll be directed to build your project (don't build until then).

We're going to author a new class to represent a custom (templated) control. We're authoring and consuming the class within the same compilation unit. But we want to be able to instantiate this class from XAML markup, and for that reason it's going to be a runtime class. And we're going to use C++/WinRT to both author and consume it.

The first step in authoring a new runtime class is to add a new Midl File (.idl) item to the project. Name it BgLabelControl.idl. Delete the default contents of BgLabelControl.idl, and paste in this runtime class declaration.

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Windows.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

The listing above shows the pattern that you follow when declaring a dependency property (DP). There are two pieces to each DP. First, you declare a read-only static property of type DependencyProperty. It has the name of your DP plus Property. You'll use this static property in your implementation. Second, you declare a read-write instance property with the type and name of your DP. If you wish to author an attached property (rather than a DP), then see the code examples in Custom attached properties.

Note

If you want a DP with a floating-point type, then make it double (Double in MIDL 3.0). Declaring and implementing a DP of type float (Single in MIDL), and then setting a value for that DP in XAML markup, results in the error Failed to create a 'Windows.Foundation.Single' from the text ''.

Save the file and build the project. During the build process, the midl.exe tool is run to create a Windows Runtime metadata file (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) describing the runtime class. Then, the cppwinrt.exe tool is run to generate source code files to support you in authoring and consuming your runtime class. These files include stubs to get you started implementing the BgLabelControl runtime class that you declared in your IDL. Those stubs are \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h and BgLabelControl.cpp.

Copy the stub files BgLabelControl.h and BgLabelControl.cpp from \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ into the project folder, which is \BgLabelControlApp\BgLabelControlApp\. In Solution Explorer, make sure Show All Files is toggled on. Right-click the stub files that you copied, and click Include In Project.

Implement the BgLabelControl custom control class

Now, let's open \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h and BgLabelControl.cpp and implement our runtime class. In BgLabelControl.h, change the constructor to set the default style key, implement Label and LabelProperty, add a static event handler named OnLabelChanged to process changes to the value of the dependency property, and add a private member to store the backing field for LabelProperty.

After adding those, your BgLabelControl.h looks like this.

// BgLabelControl.h
...
struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
    BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

    winrt::hstring Label()
    {
        return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
    }

    void Label(winrt::hstring const& value)
    {
        SetValue(m_labelProperty, winrt::box_value(value));
    }

    static Windows::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

    static void OnLabelChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
};
...

In BgLabelControl.cpp, define the static members like this.

// BgLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
);

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // Call members of the projected type via theControl.

        BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
        // Call members of the implementation type via ptr.
    }
}
...

In this walkthrough, we won't be using OnLabelChanged. But it's there so that you can see how to register a dependency property with a property-changed callback. The implementation of OnLabelChanged also shows how to obtain a derived projected type from a base projected type (the base projected type is DependencyObject, in this case). And it shows how to then obtain a pointer to the type that implements the projected type. That second operation will naturally only be possible in the project that implements the projected type (that is, the project that implements the runtime class).

Note

If you haven't installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later, then you need to call winrt::from_abi in the dependency property changed event handler above, instead of winrt::get_self.

Design the default style for BgLabelControl

In its constructor, BgLabelControl sets a default style key for itself. But what is a default style? A custom (templated) control needs to have a default style—containing a default control template—which it can use to render itself with in case the consumer of the control doesn't set a style and/or template. In this section we'll add a markup file to the project containing our default style.

Under your project node, create a new folder and name it "Themes". Under Themes, add a new item of type Visual C++ > XAML > XAML View, and name it "Generic.xaml". The folder and file names have to be like this in order for the XAML framework to find the default style for a custom control. Delete the default contents of Generic.xaml, and paste in the markup below.

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

In this case, the only property that the default style sets is the control template. The template consists of a square (whose background is bound to the Background property that all instances of the XAML Control type have), and a text element (whose text is bound to the BgLabelControl::Label dependency property).

Add an instance of BgLabelControl to the main UI page

Open MainPage.xaml, which contains the XAML markup for our main UI page. Immediately after the Button element (inside the StackPanel), add the following markup.

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

Also, add the following include directive to MainPage.h so that the MainPage type (a combination of compiling XAML markup and imperative code) is aware of the BgLabelControl custom control type. If you want to use BgLabelControl from another XAML page, then add this same include directive to the header file for that page, too. Or, alternatively, just put a single include directive in your precompiled header file.

// MainPage.h
...
#include "BgLabelControl.h"
...

Now build and run the project. You'll see that the default control template is binding to the background brush, and to the label, of the BgLabelControl instance in the markup.

This walkthrough showed a simple example of a custom (templated) control in C++/WinRT. You can make your own custom controls arbitrarily rich and full-featured. For example, a custom control can take the form of something as complicated as an editable data grid, a video player, or a visualizer of 3D geometry.

Implementing overridable functions, such as MeasureOverride and OnApplyTemplate

You derive a custom control from the Control runtime class, which itself further derives from base runtime classes. And there are overridable methods of Control, FrameworkElement, and UIElement that you can override in your derived class. Here's a code example showing you how to do that.

struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
...
    // Control overrides.
    void OnPointerPressed(Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

    // FrameworkElement overrides.
    Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
    void OnApplyTemplate() const { ... };

    // UIElement overrides.
    Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };
...
};

Overridable functions present themselves differently in different language projections. In C#, for example, overridable functions typically appear as protected virtual functions. In C++/WinRT, they're neither virtual nor protected, but you can still override them and provide your own implementation, as shown above.

Important APIs