Search code examples
uwpwinui-3c++-winrtwinui

What's the minimum required to implement a custom XAML DependencyProperty in C++/WinRT?


I'm writing WinUI 3 and UWP code, and I want to have a custom control with a DependencyProperty on it:

<local:MyControl Foo="Bar" />

I've read Custom dependency properties on MSDN, but there's a lot of content there. What's a short example with the minimum needed to implement a custom DependencyProperty in UWP or WinUI XAML, using C++/WinRT?


Solution

  • Here's my distillation of Custom dependency properties, which is worth reading anyway since it explains a lot of the nuances of Dependency Properties:

    IDL

    1. Add a static getter called FooProperty of type static Microsoft.UI.Xaml.DependencyProperty
    2. Add a getter & setter called Foo of the actual type you want.
    // MyControl.idl
    runtimeclass MyControl : Microsoft.UI.Xaml.Controls.UserControl
    {
        MyControl(); // ... etc
    
        // Add these for each property
        static Microsoft.UI.Xaml.DependencyProperty FooProperty{ get; };
        String Foo;
    }
    

    C++

    1. Implement the member getter & setter using DependencyObject::GetValue and SetValue (which should be inherited via UserControl etc).
    2. Implement the static DependencyProperty getter, which you can configure so your property can have a default value and a property-changed handler.
    3. Make sure to Register your Dependency Property.
    // MyControl.h
    namespace winrt::MyAppNamespace::implementation
    {
        struct MyControl : MyControlT<MyControl>
        {
            // ...
    
            static winrt::Microsoft::UI::Xaml::DependencyProperty FooProperty() const noexcept
            {
                return s_FooProperty;
            }
            auto Foo() const noexcept
            {
                return winrt::unbox_value<winrt::hstring>(GetValue(FooProperty()));
            }
            void Foo(winrt::hstring const& value)
            {
                SetValue(FooProperty(), winrt::box_value(value));
            }
    
        private:
            static winrt::Microsoft::UI::Xaml::DependencyProperty s_FooProperty;
        }
    }
    
    // MyControl.cpp
    using namespace winrt::MyAppNamespace::implementation;
    
    MyControl::s_FooProperty =
        winrt::Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Foo",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<winrt::MyAppNamespace::MyControl>(),
            winrt::Microsoft::UI::Xaml::PropertyMetadata{nullptr});
    

    Done! You now have a working dependency property:

    <MyControl Foo="Bar!" />
    

    Default value & changed handlers

    • If you want a static "on change" callback, add a second parameter to the PropertyMetadata constructor.
    • If you want a default value, change the first parameter to be your default value.
    // Value-changed callback
    MyControl::s_FooProperty =
        winrt::Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Foo",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<winrt::MyAppNamespace::MyControl>(),
            winrt::Microsoft::UI::Xaml::PropertyMetadata{nullptr, &MyControl::OnFooChanged});
    
    // Value-changed callback & default value
    MyControl::s_FooProperty =
        winrt::Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Foo",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<winrt::MyAppNamespace::MyControl>(),
            winrt::Microsoft::UI::Xaml::PropertyMetadata{winrt::box_value(L"Default value"), &MyControl::OnFooChanged});
    

    Note that the callback is static, not a member function. You'll have to fetch the actual instance:

    /*static*/ void MyControl::OnFooChanged(winrt::Microsoft::UI::Xaml::DependencyObject const& d, winrt::Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
    {
        auto control{ d.as<MyAppNamespace::MyControl>() };
        auto newValue{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    }
    

    See also