WPF 和 Win32 互通

本主題提供如何交互操作 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 是 Managed 程式碼,但大部分現有的 Win32 程式是以 Unmanaged C++ 撰寫。 您無法從真正的 Unmanaged 程式呼叫 WPF API。 不過,透過搭配 Microsoft Visual C++ 編譯器使用 /clr 選項,您可以建立混合 Managed-Unmanaged 程式,以便順暢地混合 Managed 和 Unmanaged API 呼叫。

一個專案層級的複雜功能是您無法將 Extensible Application Markup Language (XAML) 檔案編譯成 C++ 專案。 有數個專案部門技術可彌補這一點。

  • 建立 C# DLL,其中包含所有 XAML 頁面做為編譯的元件,然後讓 C++ 可執行檔包含該 DLL 做為參考。

  • 建立 WPF 內容的 C# 可執行檔,並讓它參考包含 Win32 內容的 C++ DLL。

  • 用來 Load 在執行時間載入任何 XAML,而不是編譯 XAML。

  • 完全不要使用 XAML,並在程式碼中撰寫所有 WPF,從 建置專案樹狀 Application 結構。

請使用最適合您的方法。

注意

如果您之前尚未使用 C++/CLI,您可能會注意到一些「新」關鍵字,例如 gcnewnullptr 和 在交互操作程式碼範例中。 這些關鍵字會取代較舊的雙底線語法 ( __gc ),並為 C++ 中的 Managed 程式碼提供更自然的語法。 若要深入瞭解 C++/CLI 受控功能,請參閱 執行時間平臺的元件延伸模組

WPF 如何使用 Hwnd

若要充分利用 WPF 「HWND Interop」,您必須瞭解 WPF 如何使用 HWND。 對於任何 HWND,您無法混合 WPF 轉譯與 DirectX 轉譯或 GDI / GDI+ 轉譯。 這有幾個隱含意義。 主要,若要完全混合使用這些轉譯模型,您必須建立交互操作方案,並使用您選擇使用之每個轉譯模型的指定區段交互操作。 此外,轉譯行為還會建立交互操作方案可完成之作業的「空間」限制。 技術領域概觀主題會更詳細地說明「空間」概念。

螢幕上的所有 WPF 元素最終都會由 HWND 支援。 當您建立 WPF Window 時,WPF 會建立最上層 HWND,並使用 HwndSource 將 和 其 WPF 內容放在 Window HWND 內。 應用程式中其餘的 WPF 內容會共用單一 HWND。 例外狀況是功能表、下拉式方塊下拉式清單和其他快顯視窗。 這些元素會建立自己的最上層視窗,這就是為什麼 WPF 功能表可能會超過包含它的視窗 HWND 邊緣的原因。 當您使用 HwndHost 將 HWND 放在 WPF 內時,WPF 會通知 Win32 如何將新的子 HWND 放置在與 WPF Window HWND 相對的位置。

在每個 HWND 之內和之間,相關的 HWND 概念都十分清楚。 技術領域概觀主題也會討論這點。

將 WPF 內容裝載在 Microsoft Win32 視窗中

在 Win32 視窗上裝載 WPF 的索引鍵是 HwndSource 類別。 這個類別會將 WPF 內容包裝在 Win32 視窗中,讓 WPF 內容可以併入 UI 做為子視窗。 下列方法結合單一應用程式中的 Win32 和 WPF。

  1. 將 WPF 內容(內容根項目)實作為 Managed 類別。 一般而言,類別繼承自其中一個類別,這些類別可以包含多個子專案和/或做為根項目,例如 DockPanelPage 。 在後續步驟中,這個類別稱為 WPF 內容類別別,而 類別的實例稱為 WPF 內容物件。

  2. 使用 C++/CLI 實作 Windows 應用程式。 如果您從現有的 Unmanaged C++ 應用程式開始,您通常可以藉由變更專案設定來包含 /clr 編譯器旗標來呼叫 Managed 程式碼(本主題並未說明支援 /clr 編譯所需的完整範圍)。

  3. 將執行緒模型設為單一執行緒 Apartment (STA)。 WPF 會使用此執行緒模型。

  4. 處理視窗程序中的 WM_CREATE 通知。

  5. 在此處理常式 (或處理常式所呼叫的函式) 內,執行下列動作︰

    1. 建立具有父視窗 HWND 做為其 parent 參數的新 HwndSource 物件。

    2. 建立 WPF 內容類別別的實例。

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

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

  6. 實作 Managed 類別,其中包含保存 WPF 內容物件的參考的靜態欄位。 這個類別可讓您從 Win32 程式碼取得 WPF 內容物件的參考,但更重要的是,它可防止不小心垃圾收集。 HwndSource

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

  8. 使用您儲存在靜態欄位中的參考來設定屬性、呼叫方法等,與 WPF 內容物件通訊。

注意

如果您產生個別的元件,則可以使用內容類別別的預設部分類別,針對 XAML 中的步驟一執行部分或所有 WPF 內容類別別定義,然後加以參考。 雖然您通常會在將 XAML 編譯成元件時包含 Application 物件,但您最終不會使用該 Application 物件做為交互操作的一部分,但您只針對應用程式所參考的 XAML 檔案使用一或多個根類別,並參考其部分類別。 此程序的其餘部分基本上與上述類似。

所有這些步驟都是透過逐步解說:在 Win32 中裝載 WPF 內容主題中的程式碼予以說明。

在 WPF 中裝載 Microsoft Win32 視窗

在其他 WPF 內容中裝載 Win32 視窗的索引鍵是 HwndHost 類別。 這個類別會將視窗包裝在 WPF 元素中,以新增至 WPF 專案樹狀結構。 HwndHost 也支援 API,可讓您執行這類工作,例如裝載視窗的處理訊息。 基本程序如下:

  1. 為 WPF 應用程式建立專案樹狀結構(可以透過程式碼或標記)。 在專案樹狀結構中尋找適當且允許的點,其中 HwndHost 可將實作新增為子項目。 在後續步驟中,這個項目稱為保留項目。

  2. 衍生自 HwndHost 以建立保存 Win32 內容的物件。

  3. 在該主機類別中,覆寫 HwndHost 方法 BuildWindowCore 。 傳回裝載之視窗的 HWND。 您可能想要將實際控制項包裝為傳回視窗的子視窗;將控制項包裝在主視窗中提供簡單的方法,讓 WPF 內容從控制項接收通知。 這項技術有助於修正有關託管控制界限之訊息處理的一些 Win32 問題。

  4. 覆寫 HwndHost 方法和 DestroyWindowCoreWndProc 。 這裡的目的是處理裝載之內容的清除和移除參考,特別是您已建立未受管理物件的參考時。

  5. 在程式碼後置檔案中,建立控制項裝載類別的執行個體,並將它設定為保留項目的子系。 一般而言,您會使用 之類的 Loaded 事件處理常式,或使用部分類別建構函式。 但是,您也可以透過執行階段行為來新增交互操作內容。

  6. 處理選取的視窗訊息,例如控制項通知。 有兩種方法。 兩者都提供訊息資料流的相同存取權,因此您的選擇大部分取決於程式設計便利性。

    • 在 方法 WndProcHwndHost 覆寫中,實作所有訊息的訊息處理(不只是關閉訊息)。

    • 讓主控 WPF 元素處理 MessageHook 事件來處理訊息。 針對傳送至裝載視窗之主要視窗程序的每個訊息,都會引發此事件。

    • 您無法使用 WndProc 處理來自進程外視窗的訊息。

  7. 使用平台叫用來呼叫 Unmanaged SendMessage 函式,以與裝載的視窗通訊。

遵循下列步驟會建立可使用滑鼠輸入的應用程式。 您可以藉由實作 介面來新增託管視窗的 IKeyboardInputSink 索引標籤支援。

所有這些步驟都是透過逐步解說:在 WPF 中裝載 Win32 控制項主題中的程式碼予以說明。

WPF 內的 Hwnd

您可以將 它視為 HwndHost 特殊控制項。 (從技術上來說, HwndHostFrameworkElement 衍生類別,不是 Control 衍生類別,但可以視為互通用途的控制項。) HwndHost 抽象化裝載內容的基礎 Win32 本質,讓 WPF 的其餘部分會將裝載的內容視為另一個類似控制項的物件,而該物件應該轉譯和處理輸入。 HwndHost 一般的行為就像任何其他 WPF FrameworkElement 一樣,不過輸出(繪圖和圖形)和輸入(滑鼠和鍵盤)有一些重要差異,但根據基礎 HWND 可以支援的限制。

輸出行為的顯著差異

  • FrameworkElement,這是 HwndHost 基類,具有相當多的屬性,表示 UI 的變更。 這些包括 的屬性,例如 FrameworkElement.FlowDirection ,它會將該專案內的元素配置變更為父代。 不過,這些屬性大多不會對應至可能的 Win32 對等專案,即使這類對等專案可能存在也一樣。 這些屬性的絶大多數和其意義太特定於轉譯技術,以致對應成為實際。 因此,設定 屬性,例如 FlowDirection on HwndHost 沒有任何作用。

  • HwndHost 無法旋轉、縮放、扭曲,或受到轉換影響。

  • HwndHost 不支援 Opacity 屬性 (Alpha 混合)。 如果 內 HwndHost 的內容會 System.Drawing 執行包含 Alpha 資訊的作業,即本身不是違規,但 HwndHost 整體只支援 Opacity = 1.0 (100%)。

  • HwndHost 將會出現在相同最上層視窗中其他 WPF 元素的頂端。 不過, ToolTipContextMenu 產生的功能表是個別的最上層視窗,因此會使用 HwndHost 正確運作。

  • HwndHost 不尊重其父 UIElement 代 的裁剪區域。 如果您嘗試將類別放在 HwndHost 捲動區域或 Canvas 內,則這可能是個問題。

輸入行為的顯著差異

  • 一般而言,雖然輸入裝置的範圍在託管的 HwndHost Win32 區域內,但輸入事件會直接移至 Win32。

  • 當滑鼠位於 上方 HwndHost 時,您的應用程式不會收到 WPF 滑鼠事件,而且 WPF 屬性 IsMouseOver 的值會是 false

  • HwndHost雖然 具有鍵盤焦點,但您的應用程式將不會收到 WPF 鍵盤事件,而且 WPF 屬性 IsKeyboardFocusWithin 的值會是 false

  • 當焦點位於 內 HwndHost ,且變更至 內 HwndHost 的另一個控制項時,您的應用程式將不會收到 WPF 事件 GotFocusLostFocus

  • 相關的手寫筆屬性和事件是類似的,而且在手寫筆結束 HwndHost 時不會報告資訊。

定位處理、助憶鍵和加速器

IKeyboardInputSinkIKeyboardInputSite 介面可讓您為混合 WPF 和 Win32 應用程式建立順暢的鍵盤體驗:

  • Win32 與 WPF 元件之間的索引標籤

  • 焦點在 Win32 元件內以及在 WPF 元件內時所使用的助憶鍵和加速器。

HwndHostHwndSource 類別都提供 的 IKeyboardInputSink 實作,但它們可能無法處理您想要用於更進階案例的所有輸入訊息。 覆寫適當的方法,以取得您想要的鍵盤行為。

介面僅支援 WPF 與 Win32 區域之間轉換時所發生的情況。 在 Win32 區域內,Tabbing 行為完全由 Win32 實作的索引標籤邏輯控制,如果有的話。

另請參閱