共用方式為


逐步解說:在 Win32 應用程式中裝載 Windows Presentation Foundation 內容

更新:2007 年 11 月

Windows Presentation Foundation (WPF) 提供用來建立應用程式的豐富環境。不過,若您已對 Win32 程式碼投入相當的心力,比較有效的方法可能是將 WPF 功能加入應用程式,而不是重新撰寫原始程式碼。WPF 提供一種直接的機制,可以將 WPF 內容裝載 (Host) 在 Win32 視窗中。

本教學課程說明如何撰寫範例應用程式:在 Win32 Window 中裝載 Windows Presentation Foundation 內容範例;這個範例應用程式將 WPF 內容裝載在 Win32 視窗中。您可以擴充這個範例以裝載任何 Win32 視窗。因為這個範例涉及混合的 Managed 和 Unmanaged 程式碼,所以應用程式是以 C++/CLI 撰寫。

這個主題包含下列章節。

  • 需求
  • 基本程序
  • 實作主應用程式
  • 實作 WPF 頁面
  • 相關主題

需求

本教學課程假設您已熟悉 WPF 和 Win32 程式設計的基本概念。如需 WPF 程式設計的基本簡介,請參閱使用者入門 (WPF)。如需 Win32 程式設計的簡介,請參考討論此主題的各種書籍,尤其是 Charles Petzold 所著的《Programming Windows》。

由於本教學課程所附的範例是以 C++/CLI 實作,因此本教學課程假設您已熟悉如何使用 C++ 來設計 Win32API,也已了解 Managed 程式碼程式設計。熟悉 C++/CLI 很有幫助,但並非絕對必要。

注意事項:

本教學課程包括摘錄自相關範例的許多程式碼範例。不過,為了方便閱讀,此課程並未包含完整的範例程式碼。如需完整的範例程式碼,請參閱在 Win32 Window 中裝載 Windows Presentation Foundation 內容範例

基本程序

本節概述用來將 WPF 內容裝載在 Win32 視窗中的基本程序。其餘各節則說明每個步驟的細節。

將 WPF 內容裝載在 Win32 視窗中的關鍵在於 HwndSource 類別。這個類別會將 WPF 內容包裝在 Win32 視窗中,讓您可以將它當做子視窗加入使用者介面 (UI) 中。下列方法會將 Win32 和 WPF 合併在單一應用程式中。

  1. 將 WPF 內容實作為 Managed 類別。

  2. 使用 C++/CLI 實作 Win32 應用程式。如果從現有的應用程式和 Unmanaged C++ 程式開始,通常可以變更專案設定以包括 /clr 編譯器 (Compiler) 旗標。

  3. 將執行緒模型設定為單一執行緒 Apartment (STA)。

  4. 在視窗程序 (Procedure) 中處理 WM_CREATE 通知,並執行下列動作:

    1. 建立新的 HwndSource 物件,並將父視窗設為其 parent 參數。

    2. 建立 WPF 內容類別的執行個體。

    3. 將 WPF 內容物件的參考指派給 HwndSourceRootVisual 屬性。

    4. 取得內容的 HWND。HwndSource 物件的 Handle 屬性包含視窗控制代碼 (HWND)。若要取得 HWND 以用於應用程式的 Unmanaged 部分,請將 Handle.ToPointer() 轉換成 HWND。

  5. 實作 Managed 類別,該類別包含存放 WPF 內容的靜態欄位。這個類別可讓您從 Win32 程式碼取得 WPF 內容的參考。

  6. 將 WPF 內容指派給靜態欄位。

  7. 藉由將處理常式附加至一個或多個 WPF 事件,從 WPF 內容接收通知。

  8. 使用儲存在靜態欄位中的參考來設定屬性等,以與 WPF 內容進行通訊。

注意事項:

您也可以使用可延伸標記語言 (XAML) 實作 WPF 內容。不過,您必須個別將它編譯成動態連結程式庫 (DLL),並從 Win32 應用程式參考該 DLL。其他步驟與上述程序類似。

實作主應用程式

本節說明如何將 WPF 內容裝載在基本的 Win32 應用程式中。此內容本身是以 C++/CLI 實作為 Managed 類別。此內容大致上是相當直接的 WPF 程式設計。有關內容實作之重要層面的討論,請參閱實作 WPF 內容。

  • 基本應用程式

  • 裝載 WPF 內容

  • 保存 WPF 內容的參考

  • 與 WPF 內容通訊

基本應用程式

裝載應用程式的起點是建立 Microsoft Visual Studio 2005 樣板。

  1. 開啟 Visual Studio 2005,然後從 [檔案] 功能表選取 [新增專案]。

  2. 從 Visual C++ 專案類型清單中選取 [Win32]。如果預設語言不是 C++,這些語言將位於 [其他語言] 底下。

  3. 選取 [Win32 專案] 樣板,指定專案名稱,然後按一下 [確定],啟動 [Win32 應用程式精靈]。

  4. 接受精靈的預設設定,然後按一下 [完成],啟動專案。

此樣板會建立基本的 Win32 應用程式,包括:

  • 應用程式的進入點 (Entry Point)。

  • 具有關聯視窗程序 (WndProc) 的視窗。

  • 具有 [檔案] 和 [說明] 標題的功能表。[檔案] 功能表包含用來關閉應用程式的 [結束] 項目。[說明] 功能表則包含用來啟動簡單對話方塊的 [關於] 項目。

開始撰寫用來裝載 WPF 內容的程式碼之前,您必須對基本樣板進行兩項修改。

第一項修改是將專案編譯為 Managed 程式碼。依預設,專案會編譯為 Unmanaged 程式碼。不過,由於 WPF 是以 Managed 程式碼實作,因此您必須適當編譯專案。

  1. 以滑鼠右鍵按一下 [方案總管] 中的專案名稱,並從內容功能表選取 [屬性],啟動 [屬性頁] 對話方塊。

  2. 從左窗格的樹狀檢視中選取 [組態屬性]。

  3. 從右窗格的 [專案預設值] 清單中選取 [Common Language Runtime] 支援。

  4. 從下拉式清單 (Drop-Down List) 方塊選取 [Common Language Runtime 支援 (/clr)]。

注意事項:

此編譯器旗標可讓您在應用程式中使用 Managed 程式碼,但是 Unmanaged 程式碼仍會依照往例進行編譯。

WPF 使用單一執行緒 Apartment (STA) 執行緒模型。為了配合 WPF 內容程式碼正常運作,您必須藉由將屬性 (Attribute) 套用至進入點,將應用程式的執行緒模型設定為 STA。

裝載 WPF 內容

WPF 內容是簡單的地址輸入應用程式,由幾個用來取得使用者名稱、地址等資料的 TextBox 控制項組成。另外還有兩個 Button 控制項,分別是 [確定] 和 [取消]。當使用者按一下 [確定] 時,此按鈕的 Click 事件處理常式便會從 TextBox 控制項收集資料,將該項資料指派至對應屬性,並引發自訂事件 OnButtonClicked。當使用者按一下 [取消] 時,處理常式只會引發 OnButtonClicked。OnButtonClicked 的事件引數物件包含 Boolean 欄位,可以指出已按下的按鈕。

用來裝載 WPF 內容的程式碼是以主視窗中 WM_CREATE 通知的處理常式實作。

GetHwnd 方法會取得大小和位置資訊,以及父視窗控制代碼,並傳回裝載之 WPF 內容的視窗控制代碼。

注意事項:

您不能使用 System::Windows::Interop 命名空間的 #using 指示詞。這樣做會讓該命名空間的 MSG 結構和 winuser.h 中宣告的 MSG 結構產生名稱衝突。您必須改用完整名稱來存取該命名空間的內容。

您不能將 WPF 內容直接裝載在應用程式視窗中,而必須先建立 HwndSource 物件來包裝 WPF 內容。這個物件基本上是設計用來裝載 WPF 內容的視窗。請建立 HwndSource 物件,做為應用程式一部分之 Win32 視窗的子系,以便將該物件裝載在父視窗中。HwndSource 建構函式參數包含的資訊與您在建立 Win32 子視窗時傳遞至 CreateWindow 的資訊大致相同。

接著,請建立 WPF 內容物件的執行個體。在這個案例中,WPF 內容是利用 C++/CLI 實作成個別類別 WPFPage。您也可以使用 XAML 實作 WPF 內容。不過,您必須設定個別專案並將 WPF 內容建置 (Build) 成 DLL,才能這樣做。您可以將該 DLL 的參考加入至專案,並使用此項參考來建立 WPF 內容的參考。

您可透過將 WPF 內容的參考指派給 HwndSourceRootVisual 屬性的內容,顯示子視窗中的 WPF 內容。

下一行程式碼會將 WPFButtonClicked 事件處理常式附加至 WPF 內容的 OnButtonClicked 事件。當使用者按一下 [確定] 或 [取消] 按鈕時,都會呼叫這個處理常式。如需此事件處理常式的進一步說明,請參閱與 WPF 內容通訊。

最後一行所示的程式碼會傳回與 HwndSource 物件關聯的視窗控制代碼 (HWND)。您可以從 Win32 程式碼使用這個控制代碼,將訊息傳送至所裝載的視窗,不過這個範例並未這樣做。HwndSource 物件每次收到訊息時都會引發事件。若要處理訊息,請呼叫 AddHook 方法,以附加訊息處理常式 (Message Handler),然後再該處理常式中處理訊息。

保存 WPF 內容的參考

在許多應用程式中,您都需要在稍後與 WPF 內容進行通訊。例如,您可能需要修改 WPF 內容屬性,或是想要讓 HwndSource 物件裝載不同的 WPF 內容。若要這樣做,您需要有 HwndSource 物件或 WPF 內容的參考。在您終結視窗控制代碼之前,HwndSource 物件及其關聯的 WPF 內容都會保留在記憶體中。不過,在您從視窗程序傳回後,指派給 HwndSource 物件的變數便會立即超出範圍。您可以使用靜態或全域變數的自訂方式來處理 Win32 應用程式的這個問題。可惜的是,您不能指派 Managed 物件給這些變數型別。您可以將與 HwndSource 物件相關聯的視窗控制代碼指派給全域或靜態變數,但是這樣做並無法提供物件本身的存取權。

解決這個問題最簡單的方法是實作 Managed 類別,而該類別應包含一組靜態欄位,其中保存所需存取之任何 Managed 物件的參考。此範例使用 WPFPageHost 類別來保存 WPF 內容的參考,以及使用者稍後可以變更之部分屬性的初始值。這個部分的定義包含在標頭中。

GetHwnd 函式的後半部會指派值給這些欄位,以供稍後 myPage 仍在範圍中時使用。

與 WPF 內容通訊

與 WPF 內容進行的通訊類型有兩種。當使用者按一下 [確定] 或 [取消] 按鈕時,應用程式會收到來自 WPF 內容的資訊。應用程式也有 UI 可供使用者變更各種 WPF 內容屬性,例如背景色彩或預設字型大小。

如前面所述,當使用者按一下其中一個按鈕時,WPF 內容都會引發 OnButtonClicked 事件。應用程式會將處理常式附加到這個事件,以接收這些通知。如果按一下 [確定] 按鈕,處理常式便會從 WPF 內容取得使用者資訊,並將它顯示在一組靜態控制項中。

處理常式會從 WPF 內容接收自訂事件引數物件 MyPageEventArgs。如果按下的是 [確定] 按鈕,此物件的 IsOK 屬性會設為 true,如果按下的是 [取消] 按鈕,則會設為 false。

如果按一下 [確定] 按鈕,處理常式會從容器 (Container) 類別取得 WPF 內容的參考,然後再收集相關 WPF 內容屬性保存的使用者資訊,並使用靜態控制項將資訊顯示在父視窗中。因為 WPF 內容資料使用 Managed 字串的格式,因此必須經過封送處理才能供 Win32 控制項使用。如果按一下 [取消] 按鈕,處理常式便會清除靜態控制項中的資料。

應用程式 UI 提供一組選項按鈕,可以讓使用者修改 WPF 內容的背景色彩,以及幾個與字型有關的屬性。下列範例摘錄自應用程式的視窗程序 (WndProc) 及其訊息處理 (用於設定不同訊息的各種屬性,包括背景色彩)。其他部分十分類似,並未顯示在此範例中。如需詳細資訊及相關內容,請參閱完整的範例。

若要設定背景色彩,請從 WPFPageHost 取得 WPF 內容 (hostedPage) 的參考,並將背景色彩屬性設定為適當色彩。此範例使用三個色彩選項:原始色彩、淺綠色或淺橙紅色。原始背景色彩已儲存為 WPFPageHost 類別中的靜態欄位。若要設定其他兩種色彩,請建立新的 SolidColorBrush 物件,並從 Colors 物件將靜態色彩值傳遞給建構函式。

實作 WPF 頁面

即使不了解實際的實作方式,您還是可以裝載及使用 WPF 內容。如果 WPF 內容已經封裝在個別 DLL 中,則此內容可能已經使用任何 Common Language Runtime (CLR) 語言建置。以下提供範例中使用之 C++/CLI 實作的簡短逐步解說。本節包含下列各小節。

  • 配置

  • 將資料傳回主視窗

  • 設定 WPF 屬性

配置

WPF 內容的 UI 項目包含五個與 Label 控制項關聯的 TextBox 控制項:[姓名]、[地址]、[城市)]、[省市] 和 [郵遞區號]。另外還有兩個 Button 控制項:[確定] 和 [取消]。

WPF 內容實作於 WPFPage 類別中。配置是以 Grid 配置項目處理。該類別繼承自 Grid,而這實際上讓它成為 WPF 內容根項目 (Root Element)。

WPF 內容建構函式會取得需要的寬度和高度,並據以調整 Grid 的大小。接著它會透過建立一組 ColumnDefinitionRowDefinition 物件,並將它們分別加入至 Grid 物件基底 ColumnDefinitionsRowDefinitions 集合,以定義基本配置。這會定義包含五個資料列和七個資料行的方格,而尺寸則由儲存格的內容決定。

接著,建構函式會將 UI 項目加入至 Grid。第一個項目是標題文字,也就是方格中第一列置中對齊的 Label 控制項。

下一列包含 [姓名] 的 Label 控制項及其相關聯的 TextBox 控制項。由於每一組標籤/文字都使用相同的程式碼,因此程式碼會放在成對的私用 (Private) 方法中,而且可用於這五組成對的標籤/文字方塊。這些方法會建立適當的控制項,並呼叫 Grid 類別的靜態 SetColumnSetRow 方法,以便將控制項放入適當的儲存格。建立控制項之後,此範例會在 GridChildren 屬性上呼叫 Add 方法,將控制加入方格中。用來加入其他各組標籤/文字方塊的程式碼也非常類似。如需詳細資訊,請參閱範例程式碼。

兩個方法的實作如下:

最後,此範例會加入 [確定] 和 [取消] 按鈕,並將事件處理常式附加到其 Click 事件。

將資料傳回主視窗

按一下其中一個按鈕時,會引發該按鈕的 Click 事件。主視窗可能只會將處理常式附加到這些事件,然後直接從 TextBox 控制項取得資料。此範例使用的方式比較不直接。它會處理 WPF 內容內的 Click,然後再引發自訂事件 OnButtonClicked,以通知 WPF 內容。這種方式可以讓 WPF 內容在通知主應用程式 (Host) 之前進行某種參數驗證工作。處理常式會從 TextBox 控制項取得文字,並將它指派給公用 (Public) 屬性,供主應用程式從中擷取資訊。

WPFPage.h 中的事件宣告:

WPFPage.cpp 中的 Click 事件處理常式:

設定 WPF 屬性

Win32 主應用程式允許使用者變更幾個 WPF 內容屬性。從 Win32 的觀點來看,這個動作只是變更屬性而已。WPF 內容類別中的實作方式較為複雜,因為它並沒有單一全域屬性可以控制所有控制項的字型。因此,使用者應該在屬性的 set 存取子中變更每個控制項的適當屬性。下列範例顯示 DefaultFontFamily 屬性的程式碼。設定此屬性會呼叫私用方法,而該方法則會設定各種控制項的 FontFamily 屬性。

從 WPFPage.h:

從 WPFPage.cpp:

請參閱

概念

WPF 和 Win32 互通性概觀

參考

HwndSource