演练:在 Win32 应用程序中承载 Windows Presentation Foundation 内容

更新:2007 年 11 月

Windows Presentation Foundation (WPF) 提供用于创建应用程序的丰富环境。但是,如果对 Win32 代码的投入较大,那么向应用程序添加 WPF 功能要比重新编写原始代码更为有效。WPF 提供了一种简单机制,用于在 Win32 窗口中承载 WPF 内容。

本教程介绍如何编写示例应用程序在 Win32 窗口中承载 Windows Presentation Foundation 内容的示例,该应用程序在 Win32 窗口中承载 WPF 内容。可以对该示例进行扩展,以承载任何 Win32 窗口。因为该示例涉及混合使用托管和非托管代码,所以使用 C++/CLI 编写应用程序。

本主题包括下列各节。

  • 要求
  • 基本过程
  • 实现宿主应用程序
  • 实现 WPF 页面
  • 相关主题

要求

本教程假定您基本熟悉 WPF 和 Win32 编程。有关 WPF 编程的基本介绍,请参见 入门 (WPF)。有关 Win32 编程的介绍,请参见关于此主题的众多书籍中的任何一本,特别推荐由 Charles Petzold 编写的 Programming Windows(《Windows 编程》)。

由于本教程中附带的示例是在 C++/CLI 中实现的,因此本教程假定您已熟悉使用 C++ 来进行 Win32 API 编程,并且了解托管代码编程。熟悉 C++/CLI 是有用的,但不是必需的。

说明:

本教程包含关联示例中的一些代码示例。但是,出于可读性目的,本教程并不包含完整的示例代码。有关完整的示例代码,请参见在 Win32 窗口中承载 Windows Presentation Foundation 内容的示例

基本过程

本节概述用于在 Win32 窗口中承载 WPF 内容的基本过程。其余各节将详细说明每个步骤。

在 Win32 窗口中承载 WPF 内容的关键是 HwndSource 类。此类在 Win32 窗口中包装 WPF 内容,从而使该窗口可以作为子窗口合并到用户界面 (UI) 中。以下方法在单个应用程序中结合使用了 Win32 和 WPF。

  1. 将 WPF 内容实现为托管类。

  2. 使用 C++/CLI 实现 Win32 应用程序。如果您要从现有应用程序和非托管 C++ 代码开始操作,您通常可以通过将项目设置更改为包含 /clr 编译器标志来使代码可以调用托管代码。

  3. 将线程处理模型设置为单线程单元 (STA)。

  4. 在窗口过程中处理 WM_CREATE 通知并执行以下操作:

    1. 创建一个新的 HwndSource 对象,并使用父窗口作为其 parent 参数。

    2. 创建 WPF 内容类的一个实例。

    3. 将对 WPF 内容对象的引用分配给 HwndSourceRootVisual 属性。

    4. 获取内容的 HWND。HwndSource 对象的 Handle 属性包含窗口句柄 (HWND)。若要获得可在应用程序的非托管部分中使用的 HWND,请将 Handle.ToPointer() 强制转换为 HWND。

  5. 实现一个包含静态字段(该字段用于保存对 WPF 内容的引用)的托管类。此类使您可以从 Win32 代码中获取对 WPF 内容的引用。

  6. 将 WPF 内容分配给该静态字段。

  7. 通过将处理程序附加到一个或多个 WPF 事件来从 WPF 内容接收通知。

  8. 通过使用您在该静态字段中存储的引用来设置属性等操作,与 WPF 内容进行通信。

说明:

使用可扩展应用程序标记语言 (XAML) 也可以实现 WPF 内容。但是,您必须将它单独编译为动态链接库 (DLL),并且从 Win32 应用程序中引用该 DLL。此过程的其余部分类似于上述过程。

实现宿主应用程序

本节描述如何在基本 Win32 应用程序中承载 WPF 内容。内容本身在 C++/CLI 中实现为托管类。这在很大程度上是简单的 WPF 编程。内容实现的关键方面在实现 WPF 内容中进行讨论。

  • 基本应用程序

  • 承载 WPF 内容

  • 保存对 WPF 内容的引用

  • 与 WPF 内容通信

基本应用程序

宿主应用程序的生成从创建 Microsoft Visual Studio 2005 模板开始。

  1. 打开 Visual Studio 2005,然后从“文件”菜单中选择“新建项目”。

  2. 从 Visual C++ 项目类型列表中选择“Win32”。如果您的默认语言不是 C++,则需要在“其他语言”下查找这些项目类型。

  3. 选择“Win32 项目”模板,为该项目指定名称,然后单击“确定”以启动“Win32 应用程序向导”。

  4. 接受向导的默认设置,然后单击“完成”以启动项目。

该模板将创建一个基本 Win32 应用程序,包括:

  • 应用程序的一个入口点。

  • 一个具有关联窗口过程 (WndProc) 的窗口。

  • 一个具有“文件”和“帮助”标题的菜单。“文件”菜单中包含用于关闭应用程序的“退出”项。“帮助”菜单中包含用于启动简单对话框的“关于”项。

在开始编写代码以承载 WPF 内容之前,需要对此基本模板进行两项修改。

第一项修改是将项目编译为托管代码。默认情况下,该项目编译为非托管代码。但是,由于 WPF 是用托管代码实现的,因此必须相应地编译该项目。

  1. 在“解决方案资源管理器”中右击项目名称,然后从上下文菜单中选择“属性”以启动“属性页”对话框。

  2. 从左窗格中的树视图中选择“配置属性”。

  3. 从右窗格中的“项目默认值”列表中选择“公共语言运行库支持”。

  4. 从下拉列表框中选择“公共语言运行库支持 (/clr)”。

说明:

此编译器标志使您可以在应用程序中使用托管代码,但您的非托管代码仍将像以前一样进行编译。

WPF 使用单线程单元 (STA) 线程处理模型。为了能正确使用 WPF 内容代码,必须通过对入口点应用属性将应用程序的线程处理模型设置为 STA。

承载 WPF 内容

WPF 内容是一个简单的地址输入应用程序。它包含若干个 TextBox 控件来获得用户名、地址等等。此外还有两个 Button 控件:“OK”(确定)和“Cancel”(取消)。当用户单击“OK”(确定)时,此按钮的 Click 事件处理程序将从 TextBox 控件收集数据,将数据分配给相应的属性,然后引发自定义事件 OnButtonClicked。当用户单击“Cancel”(取消)时,事件处理程序只是引发 OnButtonClicked。OnButtonClicked 的事件参数对象包含一个布尔型字段,指示单击了哪个按钮。

承载 WPF 内容的代码在宿主窗口中的 WM_CREATE 通知的处理程序中实现。

GetHwnd 方法获取大小和位置信息以及父窗口句柄,并返回所承载的 WPF 内容的窗口句柄。

说明:

不能对 System::Windows::Interop 命名空间使用 #using 指令。如果使用该指令,将会在该命名空间中的 MSG 结构和 winuser.h 中声明的 MSG 结构之间引起名称冲突。您必须改用完全限定名来访问该命名空间的内容。

不能直接在应用程序窗口中承载 WPF 内容。相反,应首先创建一个 HwndSource 对象来包装 WPF 内容。此对象基本上是一个专门用来承载 WPF 内容的窗口。通过将 HwndSource 对象创建为作为应用程序一部分的 Win32 窗口的子窗口,可以在父窗口中承载此对象。HwndSource 构造函数参数包含的信息与您在创建 Win32 子窗口时传递给 CreateWindow 的信息几乎相同。

接下来,需要创建 WPF 内容对象的一个实例。在此示例中,使用 C++/CLI 将 WPF 内容实现为单独的类 WPFPage。还可以使用 XAML 来实现 WPF 内容。但是,如果要这样做,需要建立一个单独的项目,并且将 WPF 内容生成为 DLL。可以将对该 DLL 的引用添加到项目中,并使用该引用来创建 WPF 内容的实例。

通过将对 WPF 内容的引用赋给 HwndSourceRootVisual 属性,可以在子窗口中显示 WPF 内容。

下一行代码将事件处理程序 WPFButtonClicked 附加到 WPF 内容 OnButtonClicked 事件。当用户单击“OK”(确定)或“Cancel”(取消)按钮时,将调用此事件处理程序。有关此事件处理程序的进一步讨论,请参见与 WPF 内容通信。

所显示的最后一行代码返回与 HwndSource 对象关联的窗口句柄 (HWND)。可以从 Win32 代码中使用此句柄向所承载的窗口发送消息(尽管该示例并未这样做)。HwndSource 对象每次收到消息时都会引发事件。若要处理消息,请调用 AddHook 方法来附加一个消息处理程序,然后在此处理程序中处理消息。

保存对 WPF 内容的引用

对于许多应用程序而言,您以后都将需要与 WPF 内容进行通信。例如,您可能需要修改 WPF 内容属性,或者可能让 HwndSource 对象承载不同的 WPF 内容。为此,您需要一个对 HwndSource 对象或 WPF 内容的引用。在您销毁窗口句柄之前,HwndSource 对象及其关联的 WPF 内容将一直保留在内存中。但是,一旦您从窗口过程返回,您分配给 HwndSource 对象的变量就将超出范围。处理 Win32 应用程序所具有的这一问题的习惯方式是使用静态或全局变量。遗憾的是,您无法为这些类型的变量分配托管对象。您可以为全局或静态变量分配与 HwndSource 对象关联的窗口句柄,但那样无法访问对象本身。

对于此问题,最简单的解决方案是实现一个托管类,该托管类包含一组静态字段,用于保存对您需要访问的任何托管对象的引用。本示例使用 WPFPageHost 类来保存对 WPF 内容的引用以及它的部分属性(用户以后可能会更改这些属性)的初始值。这些都在该类的头部定义。

GetHwnd 函数的后半部分为那些字段分配值,便于以后当 myPage 仍在范围中时使用。

与 WPF 内容通信

与 WPF 内容之间的通信有两种类型。当用户单击“OK”(确定)或“Cancel”(取消)按钮时,应用程序将从 WPF 内容接收信息。应用程序还具有 UI,使用户可以更改各种 WPF 内容属性,例如背景色或默认字体大小。

如上所述,当用户单击任一按钮时,WPF 内容将引发 OnButtonClicked 事件。应用程序将一个处理程序附加到此事件以便接收这些通知。如果用户单击了“OK”(确定)按钮,则处理程序会从 WPF 内容获取用户信息,并在一组静态控件中显示这些信息。

处理程序从 WPF 内容接收自定义事件参数对象 MyPageEventArgs。如果用户单击了“OK”(确定)按钮,则该对象的 IsOK 属性设置为 true;如果用户单击了“Cancel”(取消)按钮,则此属性设置为 false。

如果用户单击了“OK”(确定)按钮,处理程序将从容器类获取对 WPF 内容的引用。然后,处理程序收集保存在关联的 WPF 内容属性中的用户信息,并使用静态控件在父窗口中显示这些信息。由于 WPF 内容数据采用托管字符串的格式,因此必须对它进行封送才能供 Win32 控件使用。如果用户单击了“Cancel”(取消)按钮,则处理程序会清除静态控件中的数据。

应用程序 UI 提供了一组单选按钮,使用户可以修改 WPF 内容的背景色以及一些与字体相关的属性。下面的示例摘自应用程序的窗口过程 (WndProc) 及其对不同消息设置各种属性(包括背景色)的消息处理代码。其他属性的处理方式与此类似,这里不再显示。有关详细信息和上下文,请参见完整的示例。

若要设置背景色,请从 WPFPageHost 获取对 WPF 内容 (hostedPage) 的引用,并将背景色属性设置为适当的颜色。本示例使用三种颜色选项:原始颜色、淡绿色或浅橙红。原始背景色以静态字段形式存储在 WPFPageHost 类中。若要设置其他两种颜色,需要创建一个新的 SolidColorBrush 对象,并向构造函数传递 Colors 对象中的一个静态颜色值。

实现 WPF 页面

无需有关实际实现的任何知识,您就可以承载和使用 WPF 内容。如果将 WPF 内容打包到单独的 DLL 中,则可以使用任何公共语言运行时 (CLR) 语言来生成它。下面简单演练了本示例中使用的 C++/CLI 实现。本节包含下列小节。

  • 布局

  • 向宿主窗口返回数据

  • 设置 WPF 属性

布局

WPF 内容中的 UI 元素由五个 TextBox 控件组成,它们分别带有以下关联的 Label 控件:“Name”(名称)、“Address”(地址)、“City”(城市)、“State”(省/市/自治区)和“Zip”(邮政编码)。此外,还有两个 Button 控件:“OK”(确定)和“Cancel”(取消)。

WPF 内容在 WPFPage 类中实现。布局是通过 Grid 布局元素处理的。此类继承自 Grid,该类有效地使它成为 WPF 内容的根元素。

WPF 内容构造函数采用所需的宽度和高度作为参数,并相应地调整 Grid 的大小。然后,该构造函数通过创建一组 ColumnDefinitionRowDefinition 对象并将它们分别添加到 Grid 对象库 ColumnDefinitionsRowDefinitions 集合来定义基本布局。这样就定义了一个包含五个行和七个列的网格,其维度由单元格的内容确定。

接下来,构造函数将 UI 元素添加到 Grid 中。第一个元素是标题文本,它是一个 Label 控件,在网格的第一行居中放置。

下一行包含 Label 控件“Name”(名称)及其关联的 TextBox 控件。由于对每个标签/文本框对都使用相同的代码,因此将该代码放在一对私有方法中,供全部五个标签/文本框对使用。这些方法创建相应的控件,并调用 Grid 类的静态 SetColumnSetRow 方法来将控件放置在相应的单元格中。创建控件后,本示例对 GridChildren 属性调用 Add 方法以将控件添加到网格中。添加其余标签/文本框对的代码与此类似。有关详细信息,请参见示例代码。

这两个方法的实现如下所示:

最后,本示例添加“OK”(确定)和“Cancel”(取消)按钮,并向它们的 Click 事件附加事件处理程序。

向宿主窗口返回数据

单击任一按钮时,都将引发它的 Click 事件。宿主窗口可以简单地将处理程序附加到这些事件,然后直接从 TextBox 控件获取数据。本示例使用一种稍微复杂一些的方法。这种方法在 WPF 内容中处理 Click,然后引发自定义事件 OnButtonClicked 以通知 WPF 内容。这样,WPF 内容就可以在通知宿主之前进行一些参数验证。处理程序从 TextBox 控件获取文本,然后将它分配给公共属性,以便宿主可以从中检索信息。

事件声明(在 WPFPage.h 中):

Click 事件处理程序(在 WPFPage.cpp 中):

设置 WPF 属性

Win32 宿主允许用户更改一些 WPF 内容属性。从 Win32 端看来,只需更改这些属性即可。WPF 内容类中的实现要稍微复杂一些,因为没有一个全局属性能够控制所有控件的字体。需要在属性的 set 访问器中更改每个控件的相应属性。下面的示例演示与 DefaultFontFamily 属性有关的代码。设置该属性将调用一个私有方法,该方法进而设置各种控件的 FontFamily 属性。

在 WPFPage.h 中:

在 WPFPage.cpp 中:

请参见

概念

WPF 和 Win32 互操作概述

参考

HwndSource