How to create a XAML custom control in code?
Asked Answered
P

1

6

I am trying to implement a custom XAML control in code, using C++/WinRT. My attempted implementation, however, failed to compile. As a prove of concept I was using this code:

#pragma once

#include <winrt/Windows.UI.Xaml.Controls.h>

namespace MyApp
{
    struct MyControl : winrt::implements<MyControl, winrt::Windows::UI::Xaml::Controls::Control>
    {
    };
}

This resulted in the following compiler error:

1>MyControl.cpp
1>c:\program files (x86)\windows kits\10\include\10.0.17134.0\cppwinrt\winrt\base.h(6416): error C2079: 'winrt::impl::producer<D,winrt::Windows::UI::Xaml::Controls::Control,void>::vtable' uses undefined struct 'winrt::impl::produce<D,I>'
1>        with
1>        [
1>            D=MyApp::MyControl
1>        ]
1>c:\program files (x86)\windows kits\10\include\10.0.17134.0\cppwinrt\winrt\base.h(7163): note: see reference to class template instantiation 'winrt::impl::producer<D,winrt::Windows::UI::Xaml::Controls::Control,void>' being compiled
1>        with
1>        [
1>            D=MyApp::MyControl
1>        ]
1>c:\xxx\mycontrol.h(8): note: see reference to class template instantiation 'winrt::implements<MyApp::MyControl,winrt::Windows::UI::Xaml::Controls::Control>' being compiled

I am unable to understand the compiler error. Apparently, you cannot implement a XAML control the same way you would implement other types for use by the Windows Runtime.

What is required to implement a XAML custom control in code?

Pyrostat answered 12/6, 2018 at 19:52 Comment(4)
Can use C#? And I dont know how to do it in C++Kriegspiel
@lindexi: I don't want to introduce any additional dependencies, so no, no C#.Pyrostat
The implements<T> class template is pretty low level in the sense that it only helps you implement interfaces. Control is not an interface, which is why it doesn't work. You would have to list all of the interfaces that you need to implement. The trouble is that controls are pretty complicated, which is why the authoring support provided through the project templates is helpful. If nothing else, you should try the -component option to get the scaffolding generated for you.Rustle
@KennyKerr: I did try declaring my control in an .idl file (namespace MyApp { runtimeclass MyControl : Windows.UI.Xaml.Controls.Control {} }. The tooling did generate implementation files, too, but those had unexpected contents (essentially namespace winrt::MyApp::implementation { struct MyControl { MyControl() = delete; }; }). If deriving from a concrete implementation is not supported, I would have expected an error from the build tools. There's also struct ControlT in Windows.UI.Xaml.Controls.h, but I'm not sure, what that is all about.Pyrostat
G
6

"Inheriting" or "subclassing" in WinRT is subtly different from C++ inheritance. Because these are COM interfaces, when you subclass a WinRT runtimeclass, what you're really doing is COM Aggregation, combined with implementing the base type's overridable interfaces. Due to the COM aggregation aspect, this is considerably more fussy than standard C++ inheritance, what with all the delegating/nondelegating, special construction, etc. This would be a major pain in WRL, but C++/CX did a bunch of compiler magic under the hood to abstract this away. Fortunately, C++/WinRT helps you out here with providing two types of abstractions, without resorting to invisible magic.

If you are authoring a type that doesn't need to be externally visible (e.g. an app, as opposed to a runtime component) C++/WinRT provides convenient helpers for this:

#pragma once

#include <winrt/Windows.UI.Xaml.Controls.h>

namespace MyApp
{
    struct MyControl : winrt::Windows::UI::Xaml::Controls::ControlT<MyControl>
    {
        void OnTapped(winrt::Windows::UI::Xaml::Input::TappedRoutedEventArgs const&);
    };
}

This base type ControlT will correctly construct the aggregated base Control instance and delegate base methods to it, while also implementing the "overridable" interfaces. These overridable methods are all given a placeholder implementation that defaults to calling the base method, but you can override them yourself and get your custom behavior.

If, on the other hand, you need to author a type that's projected, via IDL:

namespace MyApp
{
  [default_interface]
  runtimeclass MyControl : Windows.UI.Xaml.Controls.Control
  {
    MyControl();
  };
}

That will generate similar scaffolding as the built-in ControlT case above, but also projects your type. In fact, if you examine the generated file for this type (in this example, MyControl.g.h), you'd see a MyControlT where that gets all hooked up.

(Note: the [default_interface] attribute is only needed if you have an empty, constructible, sealed runtimeclass. Once you add members, midl will figure synthesize the default interface without any other coaxing.

Gautious answered 13/6, 2018 at 20:16 Comment(5)
That was helpful, as always. Deriving from the ControlT template works for me, although all IntelliSense support is gone. Using the IDL, on the other hand, fails: error MIDL5053: [msg]empty runtime class cannot have the [composable] or [activatable] attribute [context]: [ RuntimeClass 'MyApp.MyControl' ]. When leaving out the default c'tor, the generated code contains only a struct, without any base classes. I'm using the 10.0.17125.0 SDK and the VSIX 1.0.180505.2. Have there been changes to the tooling or SDK since then?Pyrostat
Looks like you're using the new, modern IDL 3 syntax. I forgot to clarify the example. A sealed runtimeclass with only a public constructor has some ambiguitiy to the type system when it tries to synthesize the required interfaces. So, if you haven't added any members to the type, you need to specify the [default_interface] attribute.Gautious
I should clarify that this works in the official 17134 SDK. I haven't tested with the 17125 preview SDK, and I couldn't say if there are some minor differences between that preview and the release. Best to get on the supported version. :)Gautious
That was an oversight, sorry, I am using the official 17134 SDK (as evidenced by the error message in the question). Regardless, using the [default_interface] attribute did fix the MIDL error, and generated files with the expected content. Although I understand COM Aggregation, I'm still wildly confused about the anatomy of aggregated types as used by C++/WinRT. Is there any chance to get documentation on those internals published? I understand that this amounts to documenting implementation details, yet with the diagnostic story of C++ compilers, those details surface anyway.Pyrostat
Good point. I'll forward this on and get it added to our ongoing documentation effort. Since, as you said, it's implementation details, I'm not totally sure where that documentation will live, or what it will look like, but I agree that some sort of supporting info should be out there.Gautious

© 2022 - 2024 — McMap. All rights reserved.