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 是受控程式碼,但大部分現有 Win32 程式都是使用非受控程式碼 C++ 所撰寫。 您無法從真正的非受控程式呼叫 WPF API。 不過,搭配使用 /clr 選項與 Microsoft Visual C++ 編譯器,即可建立混合式受控/非受控程式,以順暢地混用受控和非受控 API 呼叫。

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

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

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

  • 使用 Load 在執行階段載入任何 XAML,而不是編譯 XAML。

  • 請完全不要使用 XAML,並使用程式碼寫入所有 WPF,以從 Application 建置項目樹狀結構。

請使用最適合您的方法。

注意

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

WPF 如何使用 Hwnd

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

HWND 最後會支援畫面上的所有 WPF 項目。 當您建立 WPF Window 時,WPF 會建立最上層 HWND,並使用 HwndSourceWindow 及其 WPF 內容放在 HWND 內。 應用程式中 WPF 內容的其餘部分會共用該單一 HWND。 例外狀況是功能表、下拉式方塊下拉式清單和其他快顯視窗。 這些項目會建立自己的最上層視窗,這就是 WPF 功能表為什麼可以通過包含它的視窗 HWND 邊緣。 當您使用 HwndHost 將 HWND 放在 WPF 內時,WPF 會通知 Win32 如何將新的子 HWND 放置在與 WPF Window HWND 相對的位置。

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

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

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

  1. 將 WPF 內容 (內容根項目) 實作為受控類別。 通常,此類別繼承自可包含多個子項目和 (或) 用作根項目的其中一個類別,例如 DockPanelPage。 在後續步驟中,這個類別指的是 WPF 內容類別,而且類別的執行個體指的是 WP 內容物件。

  2. 使用 C++/CLI 實作 Windows 應用程式。 如果您是使用現有非受控 C++ 應用程式開始進行,通常只要變更專案設定來包含 /clr 編譯器旗標,該應用程式就可以呼叫受控程式碼 (本主題未描述支援 /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. 實作受控類別,其中包含靜態欄位來保存 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. 處理選取的視窗訊息,例如控制項通知。 有兩種方法。 兩者都提供訊息資料流的相同存取權,因此您的選擇大部分取決於程式設計便利性。

    • 在覆寫 HwndHost方法WndProc 中實作所有訊息的訊息處理(不只是關機訊息)。

    • 讓主控 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 對等項目,即使這類對等項目可能存在。 這些屬性的絶大多數和其意義太特定於轉譯技術,以致對應成為實際。 因此,在 HwndHost 上設定 FlowDirection 之類的屬性沒有任何作用。

  • HwndHost 不能進行旋轉、縮放、扭曲,也不會受到轉換影響。

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

  • HwndHost 將出現在相同最上層視窗中其他 WPF 項目的上方。 不過,產生的 ToolTipContextMenu 功能表是個別的最上層視窗,因此會與 HwndHost 正確運作。

  • HwndHost 不尊重其父系 UIElement 的裁剪區域。 如果您嘗試將 HwndHost 類別放在捲動區域或 Canvas,這可能會造成問題。

輸入行為的顯著差異

  • 一般而言,輸入裝置位在 HwndHost 所裝載的 Win32 區域內時,輸入事件會直接移至 Win32。

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

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

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

  • 相關的手寫筆屬性和事件類似,而且不會在手寫筆停留在 HwndHost 上方時報告資訊。

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

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

  • Win32 與 WPF 元件之間的定位處理

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

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

這些介面僅支援在 WPF 與 Win32 區域之間轉換時所發生的情況。 在 Win32 區域內,定位處理行為完全是透過 Win32 實作的定位處理邏輯所控制 (如果有的話)。

另請參閱