如何在代码中创建 XAML 自定义控件?
How to create a XAML custom control in code?
我正在尝试使用 C++/WinRT 在代码中实现自定义 XAML 控件。然而,我尝试的实现未能编译。作为概念证明,我使用了这段代码:
#pragma once
#include <winrt/Windows.UI.Xaml.Controls.h>
namespace MyApp
{
struct MyControl : winrt::implements<MyControl, winrt::Windows::UI::Xaml::Controls::Control>
{
};
}
这导致了以下编译器错误:
1>MyControl.cpp
1>c:\program files (x86)\windows kits\include.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\include.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
我无法理解编译器错误。显然,您不能像实现 Windows 运行时使用的其他类型那样实现 XAML 控件。
在代码中实现 XAML 自定义控件需要什么?
WinRT 中的 "Inheriting" 或 "subclassing" 与 C++ 继承略有不同。因为这些是 COM 接口,所以当您子类化 WinRT 运行时类时,您真正做的是 COM Aggregation, combined with implementing the base type's overridable interfaces。由于 COM 聚合方面,这比标准 C++ 继承要复杂得多,所有 delegating/nondelegating、特殊构造等。这将是 WRL 中的主要痛苦,但 C++/CX 做了一堆编译器引擎盖下的魔法将其抽象出来。幸运的是,C++/WinRT 可以帮助您提供两种类型的抽象,而无需诉诸无形的魔法。
如果您正在创作不需要在外部可见的类型(例如应用程序,而不是运行时组件)C++/WinRT 为此提供了方便的帮助程序:
#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&);
};
}
此基类型 ControlT
将正确构造聚合基 Control
实例并将基方法委托给它,同时还实现 "overridable" 接口。这些可覆盖的方法都被赋予一个占位符实现,默认调用基本方法,但您可以自己覆盖它们并获得您的自定义行为。
另一方面,如果您需要通过 IDL 创建一个投影的类型:
namespace MyApp
{
[default_interface]
runtimeclass MyControl : Windows.UI.Xaml.Controls.Control
{
MyControl();
};
}
这将生成与上面内置 ControlT
案例类似的脚手架,但也会投影您的类型。事实上,如果您检查为这种类型生成的文件(在这个例子中,MyControl.g.h),您会看到一个 MyControlT
,其中所有内容都已连接。
(注意:仅当您有一个空的、可构造的、密封的运行时类时才需要 [default_interface]
属性。添加成员后,midl 将合成默认接口,无需任何其他操作。
我正在尝试使用 C++/WinRT 在代码中实现自定义 XAML 控件。然而,我尝试的实现未能编译。作为概念证明,我使用了这段代码:
#pragma once
#include <winrt/Windows.UI.Xaml.Controls.h>
namespace MyApp
{
struct MyControl : winrt::implements<MyControl, winrt::Windows::UI::Xaml::Controls::Control>
{
};
}
这导致了以下编译器错误:
1>MyControl.cpp 1>c:\program files (x86)\windows kits\include.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\include.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
我无法理解编译器错误。显然,您不能像实现 Windows 运行时使用的其他类型那样实现 XAML 控件。
在代码中实现 XAML 自定义控件需要什么?
"Inheriting" 或 "subclassing" 与 C++ 继承略有不同。因为这些是 COM 接口,所以当您子类化 WinRT 运行时类时,您真正做的是 COM Aggregation, combined with implementing the base type's overridable interfaces。由于 COM 聚合方面,这比标准 C++ 继承要复杂得多,所有 delegating/nondelegating、特殊构造等。这将是 WRL 中的主要痛苦,但 C++/CX 做了一堆编译器引擎盖下的魔法将其抽象出来。幸运的是,C++/WinRT 可以帮助您提供两种类型的抽象,而无需诉诸无形的魔法。
如果您正在创作不需要在外部可见的类型(例如应用程序,而不是运行时组件)C++/WinRT 为此提供了方便的帮助程序:
#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&);
};
}
此基类型 ControlT
将正确构造聚合基 Control
实例并将基方法委托给它,同时还实现 "overridable" 接口。这些可覆盖的方法都被赋予一个占位符实现,默认调用基本方法,但您可以自己覆盖它们并获得您的自定义行为。
另一方面,如果您需要通过 IDL 创建一个投影的类型:
namespace MyApp
{
[default_interface]
runtimeclass MyControl : Windows.UI.Xaml.Controls.Control
{
MyControl();
};
}
这将生成与上面内置 ControlT
案例类似的脚手架,但也会投影您的类型。事实上,如果您检查为这种类型生成的文件(在这个例子中,MyControl.g.h),您会看到一个 MyControlT
,其中所有内容都已连接。
(注意:仅当您有一个空的、可构造的、密封的运行时类时才需要 [default_interface]
属性。添加成员后,midl 将合成默认接口,无需任何其他操作。