在 C++ 桌面 (Win32) 应用中使用 WinRT XAML 托管 API

重要

本主题使用或提及 CommunityToolkit/Microsoft.Toolkit.Win32 GitHub 存储库中的类型。 有关 XAML Islands 支持的重要信息,请参阅该存储库中的 XAML Islands 通知

从 Windows 10 版本 1903 开始,非 UWP 桌面应用(包括 C++ 桌面 (Win32) 应用、WPF 和 Windows 窗体应用)可以使用 WinRT XAML 托管 API,在与窗口句柄 (HWND) 相关联的任何 UI 元素中托管 WinRT XAML 控件。 此 API 使非 UWP 桌面应用可以使用仅可通过 WinRT XAML 控件获取的最新 Windows UI 功能。 例如,非 UWP 桌面应用可以使用此 API 来托管使用 Fluent Design System 并支持 Windows Ink 的 WinRT XAML 控件。

WinRT XAML 托管 API 为我们提供更广泛的控件使开发人员能够将 Fluent UI 引入非 UWP 桌面应用奠定了基础。 此功能称为“XAML 岛”。 有关此功能的概述,请参阅在桌面应用中托管 WinRT XAML 控件(XAML 岛)

注意

如果有关于 XAML 岛的反馈,请在 Microsoft.Toolkit.Win32 存储库中创建一个新问题,将你的意见留在那里。

WinRT XAML 托管 API 是否适合你的桌面应用?

WinRT XAML 托管 API 提供了用于在桌面应用中托管 WinRT XAML 控件的低级别基础结构。 某些类型的桌面应用可以选择使用其他更方便的 API 来实现此目标。

  • 如果你拥有 C++ 桌面应用且想要在应用中托管 WinRT XAML 控件,则必须使用 WinRT XAML 托管 API。 这些类型的应用没有其他选择。

  • 对于 WPF 和 Windows 窗体应用,强烈建议你使用 Windows 社区工具包中的 XAML 岛 .NET 控件,而不是直接使用 WinRT XAML 托管 API。 这些控件在内部使用 WinRT XAML 托管 API,并实现你在直接使用 WinRT XAML 托管 API 时需要自行处理的所有行为,包括键盘导航和布局更改。

因为我们建议仅 C++ 桌面应用使用 WinRT XAML 托管 API,所以本文主要提供适用于 C++ 桌面应用的说明和示例。 但是,你可以选择在 WPF 和 Windows 窗体应用中使用 WinRT XAML托管 API。 本文指向适用于 Windows 社区工具包中 WPF 和 Windows 窗体的主机控件的相关源代码,使你了解这些控件如何使用 WinRT XAML 托管 API。

了解如何使用 XAML 托管 API

若要按照提供了代码示例的分步说明在 C++ 桌面应用中使用 XAML 托管 API,请参阅以下文章:

示例

在你的代码中使用 WinRT XAML 托管 API 的方式取决于你的应用类型、应用的设计以及其他因素。 为了帮助说明如何在完整应用的上下文中使用此 API,本文引用了以下示例中的代码。

C++ 桌面 (Win32)

以下示例演示如何在 C++ 桌面应用中使用 WinRT XAML 托管 API:

  • 简单 XAML 岛示例。 此示例演示了在未打包的 C++ 桌面应用中托管 WinRT XAML 控件的基本实现。

  • 带有自定义控件示例的 XAML 岛。 此示例演示了在打包的 C++ 桌面应用中托管自定义 WinRT XAML 控件以及处理其他行为(例如键盘输入和焦点导航)的完整实现。

WPF 和 Windows 窗体

Windows 社区工具包中的 WindowsXamlHost 控件用作在 WPF 和 Windows 窗体应用中使用 WinRT XAML 托管 API 的参考示例。 源代码在以下位置提供:

注意

强烈建议你使用 Windows 社区工具包中的 XAML 岛 .NET 控件,而不是直接在 WPF 和 Windows 窗体应用中使用 WinRT XAML 托管 API。 本文中的 WPF 和 Windows 窗体示例链接仅用于说明目的。

API 的体系结构

WinRT XAML 托管 API 包含这些主要的 Windows 运行时类型和 COM 接口。

类型或接口 描述
WindowsXamlManager 此类表示 UWP XAML 框架。 此类提供单个静态 InitializeForCurrentThread 方法,该方法可在桌面应用中的当前线程上初始化 UWP XAML 框架。
DesktopWindowXamlSource 此类表示你在桌面应用中托管的 UWP XAML 内容的实例。 此类最重要的成员是 Content 属性。 需将此属性分配给要托管的 Windows.UI.Xaml.UIElement。 此类还有其他成员,这些成员用于将键盘焦点导航入和导航出 XAML 岛。
IDesktopWindowXamlSourceNative 此 COM 接口提供 AttachToWindow 方法,可使用该方法将应用中的 XAML 岛附加到父 UI 元素。 每个 DesktopWindowXamlSource 对象都实现此接口。
IDesktopWindowXamlSourceNative2 此 COM 接口提供 PreTranslateMessage 方法,该方法使 UWP XAML 框架能够正确处理某些 Windows 消息。 每个 DesktopWindowXamlSource 对象都实现此接口。

下图说明了在桌面应用中托管的 XAML 岛中对象的层次结构。

  • 基本级别是你希望在其中托管 XAML 岛的应用中的 UI 元素。 此 UI 元素必须具有一个窗口句柄 (HWND)。 可在其中托管 XAML Island 的 UI 元素示例包括适用于 C++ 桌面应用的窗口、适用于 WPF 应用的 System.Windows.Interop.HwndHost,以及适用于 Windows 窗体应用的 System.Windows.Forms.Control

  • 下一级别是 DesktopWindowXamlSource 对象。 此对象提供用于托管 XAML 岛的基础结构。 你的代码负责创建此对象并将其附加到父 UI 元素。

  • 创建 DesktopWindowXamlSource 时,此对象将自动创建一个本机子窗口以托管 WinRT XAML 控件。 此本机子窗口主要提取自代码,但如果需要,你可以访问其句柄 (HWND)。

  • 最后,最高级别是要在桌面应用中托管的 WinRT XAML 控件。 这可以是派生自 Windows.UI.Xaml.UIElement 的任何 UWP 对象,包括 Windows SDK 提供的任何 WinRT XAML 控件以及自定义用户控件。

DesktopWindowXamlSource architecture

注意

在桌面应用中托管 XAML 岛时,可以同时在同一线程上运行多个 XAML 内容树。 若要访问 XAML 岛中 XAML 内容树的根元素并获取在其中托管它的上下文的相关信息,请使用 XamlRoot 类。 CoreWindowApplicationView窗口 API 不会为 XAML 岛提供正确的信息。 有关详细信息,请参阅此部分

最佳做法

使用 WinRT XAML 托管 API 时,请针对托管 WinRT XAML 控件的每个线程遵循以下最佳做法:

故障排除

在 UWP 应用中使用 WinRT XAML 托管 API 时出错

问题 解决方法
你的应用收到 COMException,显示消息:“无法激活 DesktopWindowXamlSource。 此类型不能在 UWP 应用中使用“或者”无法激活 WindowsXamlManager。 此类型不能在 UWP 应用中使用。” 此错误表示你正尝试在 UWP 应用中使用 WinRT XAML 托管 API(具体来说,你正尝试实例化 DesktopWindowXamlSourceWindowsXamlManager 类型)。 WinRT XAML 托管 API 仅适用于非 UWP 桌面应用,例如 WPF、Windows 窗体和 C++ 桌面应用程序。

尝试使用 WindowsXamlManager 或 DesktopWindowXamlSource 类型时出错

问题 解决方法
你的应用收到一个异常,显示消息“面向 Windows 版本 10.0.18226.0 及更高版本的应用支持 WindowsXamlManager 和 DesktopWindowXamlSource。 请检查应用程序清单或程序包清单,确保 MaxTestedVersion 属性已更新。” 此错误表示你的应用程序尝试在 WinRT XAML 托管 API 中使用 WindowsXamlManager 或 DesktopWindowXamlSource 类型,但 OS 无法确定该应用是否是面向 Windows 10 版本 1903 或更高版本而构建的 。 在早期版本的 Windows 10 中,WinRT XAML 托管 API 最初以预览版的形式引入,仅从 Windows 10 版本 1903 开始才受支持。

若要解决此问题,请为应用创建 MSIX 包,并从该包运行应用,或在项目中安装 Microsoft.Toolkit.Win32.UI.SDK NuGet 包。

附加到不同线程上的窗口时出错

问题 解决方法
你的应用收到 COMException,显示消息:“AttachToWindow 方法失败,因为指定的 HWND 是在另一个线程上创建的。” 此错误表示你的应用程序调用了 IDesktopWindowXamlSourceNative::AttachToWindow 方法,并向其传递了在不同线程上创建的窗口的 HWND。 必须向此方法传递在与调用该方法的代码相同的线程上创建的窗口的 HWND。

附加到不同最高级别窗口上的窗口时出错

问题 解决方法
你的应用收到 COMException,显示消息:“AttachToWindow 方法失败,因为指定的 HWND 来自于与先前在同一线程上传递给 AttachToWindow 的 HWND 不同的最高级别窗口。” 此错误表示你的应用程序调用了 IDesktopWindowXamlSourceNative::AttachToWindow 方法,并向其传递了一个窗口的 HWND,该 HWND 来自于与你在同一线程上对此方法的先前调用中所指定的窗口不同的最高级别窗口。

在应用程序调用特定线程上的 AttachToWindow 后,同一线程上的所有其他 DesktopWindowXamlSource 对象只能附加到首次调用时传递到 AttachToWindow 的相同最高级别窗口的后代窗口 。 当特定线程的所有 DesktopWindowXamlSource 对象均关闭时,下一个 DesktopWindowXamlSource 就可以自由附加到任何窗口 。

若要解决此问题,请关闭与此线程上的其他最高级别窗口绑定的所有 DesktopWindowXamlSource 对象,或为此 DesktopWindowXamlSource 创建一个新线程 。