本主题提供了一个关于如何进行 Windows Presentation Foundation (WPF)与 Win32 代码互操作的概述。 WPF 提供了用于创建应用程序的丰富环境。 但是,当你对 Win32 代码进行大量投资时,重用其中一些代码可能更有效。
WPF 和 Win32 互作基础知识
WPF 和 Win32 代码之间的互作有两种基本技术。
在 Win32 窗口中托管 WPF 内容。 借助此方法,可以在标准 Win32 窗口和应用程序的框架中使用 WPF 的高级图形功能。
在 WPF 内容中托管 Win32 窗口。 通过此方法,可以在其他 WPF 内容的上下文中使用现有的自定义 Win32 控件,并将数据传递到边界。
本主题从概念上介绍了上述每一种技术。 有关在 Win32 中托管 WPF 的更面向代码的插图,请参阅 演练:在 Win32 中托管 WPF 内容。 有关在 WPF 中托管 Win32 的更面向代码的插图,请参阅 演练:在 WPF 中托管 Win32 控件。
WPF 互操作项目
WPF API 是托管代码,但大多数现有的 Win32 程序都是以非托管C++编写的。 无法从真正的非托管程序调用 WPF API。 但是,通过将 /clr
选项与 Microsoft Visual C++ 编译器结合使用,可以创建混合托管非托管程序,以便无缝混合托管和非托管 API 调用。
一个项目级的复杂性是,不能将可扩展应用程序标记语言(XAML)文件编译为C++项目。 有几个项目划分技术可以弥补这一点。
创建一个 C# DLL,其中包含所有 XAML 页面作为编译的程序集,然后将C++可执行文件包含该 DLL 作为引用。
为 WPF 内容创建 C# 可执行文件,并使其引用包含 Win32 内容的C++ DLL。
使用 Load 在运行时加载任何 XAML,而不是编译您的 XAML。
完全不使用 XAML,在代码中编写所有的 WPF,从Application开始构建元素树。
使用最适合你的任何方法。
注释
如果以前没有使用过 C++/CLI,你可能会注意到一些“新”关键字,例如gcnew
nullptr
,在互作代码示例中。 这些关键字取代了较旧的双下划线语法(__gc
),并为C++中的托管代码提供更自然的语法。 若要详细了解 C++/CLI 托管功能,请参阅 运行时平台的组件扩展。
WPF 如何使用 Hwnds
若要充分利用 WPF“HWND 互操作”,需要了解 WPF 如何使用 HWND。 对于任何 HWND,您不能将 WPF 渲染与 DirectX 渲染或 GDI/GDI+ 渲染混合使用。 这具有许多影响。 主要是为了能混合这些呈现模型,您必须创建一个互操作解决方案,并为您选择使用的每个呈现模型指定相应的互操作段。 此外,呈现行为为互操作解决方案可以实现的功能创建了一个“空域”限制。 “领空”概念在“ 技术区域概述”主题中进行了更详细的说明。
屏幕上的所有 WPF 元素最终都由 HWND 提供支持。 创建 WPF Window时,WPF 会创建一个顶级 HWND,并利用一个 HwndSource 将 Window 及其 WPF 内容放入此 HWND 中。 应用程序中剩余的 WPF 内容共享单一 HWND。 例外是菜单、组合框下拉列表和其他弹出窗口。 这些元素创建自己的顶级窗口,这就是为什么 WPF 菜单可能超过包含它的窗口 HWND 边缘的原因。 用于 HwndHost 在 WPF 中放置 HWND 时,WPF 会通知 Win32 如何相对于 WPF Window HWND 定位新的子 HWND。
与 HWND 相关的一个概念是 HWND 内部及其之间的透明性。 这在 “技术区域概述”主题中也进行了讨论。
在 Microsoft Win32 窗口中托管 WPF 内容
在 Win32 窗口中托管 WPF 的密钥是 HwndSource 类。 此类将 WPF 内容包装在 Win32 窗口中,以便可以将 WPF 内容作为子窗口嵌入到用户界面中。 以下方法将 Win32 和 WPF 合并到单个应用程序中。
将 WPF 内容(内容根元素)实现为托管类。 通常,该类继承自可包含多个子元素和/或用作根元素的类之一,例如 DockPanel 或 Page。 在后续步骤中,此类称为 WPF 内容类,类的实例称为 WPF 内容对象。
使用 C++/CLI 实现 Windows 应用程序。 要使现有的未管理的 C++ 应用程序能够调用托管代码,通常可以通过更改项目设置以包含
/clr
编译器标志来实现(本主题未详细说明支持/clr
编译所需的所有内容)。将线程模型设置为单线程单元(STA)。 WPF 使用此线程模型。
在窗口过程中处理WM_CREATE通知。
在处理程序(或处理程序调用的函数)中执行以下作:
使用父窗口 HWND 作为其
parent
参数创建新HwndSource对象。创建 WPF 内容类的实例。
将对 WPF 内容对象的引用分配给 HwndSource 对象 RootVisual 属性。
对象HwndSourceHandle属性包含窗口句柄(HWND)。 若要获取可在应用程序的非托管部分中使用的 HWND,请将
Handle.ToPointer()
强制转换为 HWND。
实现一个托管类,其中包含一个静态字段,该字段保存对您的 WPF 内容对象的引用。 此类允许你从 Win32 代码获取 WPF 内容对象的引用,但更重要的是,它可以防止 HwndSource 被意外垃圾回收。
通过将处理程序附加到一个或多个 WPF 内容对象事件来接收来自 WPF 内容对象的通知。
通过使用存储在静态字段中的引用来设置属性、调用方法等,与 WPF 内容对象通信。
注释
如果您生成并引用单独的程序集,则可以在 XAML 中使用内容类的默认分部类来定义步骤 1 的部分或全部 WPF 内容类。 虽然通常会在将 XAML 编译为程序集的过程中包含对象 Application,但该对象并未被用作互操作的一部分,而只是应用程序引用的 XAML 文件中,一个或多个根类以及引用其部分类的使用。 该过程的其余部分实质上类似于上述过程。
其中每个步骤都演示了主题 演练:在 Win32 中托管 WPF 内容的代码。
在 WPF 中托管 Microsoft Win32 窗口
在其他 WPF 内容中托管 Win32 窗口的密钥是 HwndHost 类。 此类将窗口封装为 WPF 元素,以便将其添加到 WPF 元素树中。 HwndHost 还支持允许你执行托管窗口的进程消息等任务的 API。 基本过程:
为 WPF 应用程序创建元素树(可以通过代码或标记)。 在元素树中寻找一个合适且许可的节点,以在其中添加 HwndHost 的实现作为子元素。 在这些步骤的其余部分中,此元素称为保留元素。
从 HwndHost 派生,创建一个对象,用来保存 Win32 内容。
在该主机类中,重写 HwndHost 方法 BuildWindowCore。 返回托管窗口的 HWND。 你可能希望将实际控件包装为返回窗口的子窗口;将控件包装在主机窗口中为 WPF 内容提供一种从控件接收通知的简单方法。 此方法有助于更正有关托管控制边界的消息处理的某些 Win32 问题。
重写 HwndHost 方法、DestroyWindowCore 和 WndProc。 此处的意图是处理对托管内容的清理和删除引用,尤其是在你创建了对非托管对象的引用时。
在代码隐藏文件中,创建控件宿主类的实例,并将其设为保留元素的子级。 通常使用事件处理程序,例如 Loaded,或使用分部类构造函数。 但你也可以通过运行时行为添加互作内容。
处理所选窗口消息,例如控制通知。 方法有两种。 两者都提供对消息流的相同访问权限,因此选择在很大程度上是编程便利性问题。
让宿主 WPF 元素通过处理 MessageHook 事件来处理消息。 对于发送到托管窗口的主窗口过程的每个消息,都会引发此事件。
无法使用WndProc处理来自进程外窗口的消息。
使用平台调用来调用非托管
SendMessage
函数,与托管窗口进行通信。
按照以下步骤创建一个适用于鼠标输入的应用程序。 可以通过实现 IKeyboardInputSink 接口来为托管窗口添加制表符支持。
其中每个步骤都演示了主题 演练:在 WPF 中托管 Win32 控件的代码。
WPF 中的 Hwnds
你可以将其 HwndHost 视为特殊控件。 (从技术上讲, HwndHost 是 FrameworkElement 派生类,不是 Control 派生类,但它可以被视为用于互作的控件。) HwndHost 抽象化托管内容的基础 Win32 性质,以便 WPF 的其余部分将托管内容视为另一个类似控件的对象,该对象应呈现和处理输入。 HwndHost 通常表现得像其他 WPF FrameworkElement,然而在输出(绘图和图形)和输入(鼠标和键盘)方面,基于基础 HWND 支持的限制存在一些重要差异。
输出行为方面的显著差异
FrameworkElement,即 HwndHost 基类,具有相当多的属性表示对 UI 的更改。 这些属性包括 FrameworkElement.FlowDirection,该属性会更改其中元素的布局,使其作为父元素。 但是,这些属性中的大多数都未映射到可能的 Win32 等效项,即使此类等效项可能存在。 这些属性及其含义中有太多过于依赖于渲染技术的特性,因此无法有效地进行映射。 因此,在HwndHost上设置属性(例如FlowDirection)不起作用。
HwndHost 不能旋转、缩放、倾斜或以其他方式受到变换影响。
HwndHost 不支持 Opacity 属性(alpha 混合)。 如果 HwndHost 中的内容执行 System.Drawing 操作并包含 alpha 信息,那本身并不是违规,但整个 HwndHost 仅支持 Opacity = 1.0 (100%)。
HwndHost 将显示在同一顶级窗口中其他 WPF 元素的顶部。 但是, ToolTip 或 ContextMenu 生成的菜单是单独的顶级窗口,因此将正确使用 HwndHost。
HwndHost 不遵循其父 UIElement 的剪辑区域。 这可能会成为一个问题,如果您尝试将 HwndHost 类置于滚动区域或 Canvas 中。
输入行为中的显著差异
一般情况下,虽然输入设备被局限在 Win32 托管区域 HwndHost 中,但输入事件会直接转到 Win32。
鼠标悬停在 HwndHost 时,您的应用程序不会收到 WPF 鼠标事件,并且 WPF 属性 IsMouseOver 的值将是
false
。HwndHost虽然具有键盘焦点,但应用程序不会接收 WPF 键盘事件,WPF 属性IsKeyboardFocusWithin的值将是
false
。当焦点位于HwndHost内,并切换到HwndHost中的另一个控件时,您的应用程序将无法接收到WPF事件GotFocus或LostFocus。
相关的触笔属性和事件是类似的,在触笔结束 HwndHost时不报告信息。
制表、助记符和快捷键
可以使用 IKeyboardInputSink 和 IKeyboardInputSite 接口为混合 WPF 和 Win32 应用程序创建无缝的键盘体验:
在 Win32 和 WPF 组件之间进行切换操作
当焦点位于 Win32 组件和 WPF 组件内时,助记键和加速器都起作用。
HwndHost 和 HwndSource 类都提供了对 IKeyboardInputSink 的实现,但它们可能无法处理在复杂场景中所需的所有输入消息。 重写适当的方法以获取想要的键盘行为。
接口仅支持 WPF 和 Win32 区域之间的转换时发生的情况。 在 Win32 区域中,Tabbing 行为完全由 Win32 实现的逻辑控制(如果有)。