在 WPF 中裝載 Win32 內容
必要條件
請參閱 WPF 和 Win32 交互操作。
Windows 簡報架構內的 Win32 逐步解說 (HwndHost)
若要重複使用 WPF 應用程式內的 Win32 內容,請使用 HwndHost ,這是讓 HWND 看起來像 WPF 內容的控制項。 和 一樣 HwndSource , HwndHost 直接使用:衍生自 HwndHost 並實 BuildWindowCore
作 和 DestroyWindowCore
方法,然後具現化衍生 HwndHost 類別,並將其放在 WPF 應用程式內。
如果您的 Win32 邏輯已封裝為控制項,則您的 BuildWindowCore
實作就不只是對 CreateWindow
的呼叫。 例如,若要在 C++ 中建立 Win32 LISTBOX 控制項:
virtual HandleRef BuildWindowCore(HandleRef hwndParent) override {
HWND handle = CreateWindowEx(0, L"LISTBOX",
L"this is a Win32 listbox",
WS_CHILD | WS_VISIBLE | LBS_NOTIFY
| WS_VSCROLL | WS_BORDER,
0, 0, // x, y
30, 70, // height, width
(HWND) hwndParent.Handle.ToPointer(), // parent hwnd
0, // hmenu
0, // hinstance
0); // lparam
return HandleRef(this, IntPtr(handle));
}
virtual void DestroyWindowCore(HandleRef hwnd) override {
// HwndHost will dispose the hwnd for us
}
但假設 Win32 程式碼不是完全獨立的? 若是如此,您可以建立 Win32 對話方塊,並將其內容內嵌至較大的 WPF 應用程式。 此範例會在 Visual Studio 和 C++ 中顯示此專案,不過您也可以使用不同的語言或命令列來執行此動作。
從簡單對話方塊開始,該對話方塊會編譯成 C++ DLL 專案。
接下來,將對話方塊介紹至較大的 WPF 應用程式:
將 DLL 編譯為 Managed (
/clr
)將對話方塊轉換成控制項
使用 和
DestroyWindowCore
方法定義 的 HwndHostBuildWindowCore
衍生類別覆寫
TranslateAccelerator
方法來處理對話方塊索引鍵覆寫
TabInto
方法以支援索引標籤覆寫
OnMnemonic
方法以支援助憶鍵具現化 HwndHost 子類別,並將它放在正確的 WPF 元素之下
將對話方塊轉換成控制項
您可以使用WS_CHILD和DS_CONTROL樣式,將對話方塊轉換成子 HWND。 移至定義對話方塊的資源檔 (.rc),並尋找對話方塊定義的開頭:
IDD_DIALOG1 DIALOGEX 0, 0, 303, 121
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
將第二行變更為:
STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL
此動作不會完全封裝成獨立控制項;您仍然需要呼叫 IsDialogMessage()
,讓 Win32 可以處理某些訊息,但控制項變更確實提供直接的方式,將這些控制項放在另一個 HWND 內。
子類別 HwndHost
匯入下列命名空間:
namespace ManagedCpp
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Input;
using namespace System::Windows::Media;
using namespace System::Runtime::InteropServices;
然後建立 的 HwndHost 衍生類別,並覆寫 BuildWindowCore
和 DestroyWindowCore
方法:
public ref class MyHwndHost : public HwndHost, IKeyboardInputSink {
private:
HWND dialog;
protected:
virtual HandleRef BuildWindowCore(HandleRef hwndParent) override {
InitializeGlobals();
dialog = CreateDialog(hInstance,
MAKEINTRESOURCE(IDD_DIALOG1),
(HWND) hwndParent.Handle.ToPointer(),
(DLGPROC) About);
return HandleRef(this, IntPtr(dialog));
}
virtual void DestroyWindowCore(HandleRef hwnd) override {
// hwnd will be disposed for us
}
在這裡,您會使用 CreateDialog
來建立真正是控制項的對話方塊。 由於這是 DLL 內呼叫的第一個方法之一,因此您也應該呼叫一些標準 Win32 初始化,方法是呼叫稍後將定義的函式,稱為 InitializeGlobals()
:
bool initialized = false;
void InitializeGlobals() {
if (initialized) return;
initialized = true;
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_TYPICALWIN32DIALOG, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
覆寫 TranslateAccelerator 方法來處理對話方塊索引鍵
如果您現在執行此範例,則會取得顯示對話方塊控制項,但會忽略讓對話方塊成為功能對話方塊的所有鍵盤處理。 您現在應該覆寫 TranslateAccelerator
實作 (來自 IKeyboardInputSink
的 介面 HwndHost )。 當應用程式收到WM_KEYDOWN和WM_SYSKEYDOWN時,就會呼叫這個方法。
#undef TranslateAccelerator
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
ModifierKeys modifiers) override
{
::MSG m = ConvertMessage(msg);
// Win32's IsDialogMessage() will handle most of our tabbing, but doesn't know
// what to do when it reaches the last tab stop
if (m.message == WM_KEYDOWN && m.wParam == VK_TAB) {
HWND firstTabStop = GetDlgItem(dialog, IDC_EDIT1);
HWND lastTabStop = GetDlgItem(dialog, IDCANCEL);
TraversalRequest^ request = nullptr;
if (GetKeyState(VK_SHIFT) && GetFocus() == firstTabStop) {
// this code should work, but there’s a bug with interop shift-tab in current builds
request = gcnew TraversalRequest(FocusNavigationDirection::Last);
}
else if (!GetKeyState(VK_SHIFT) && GetFocus() == lastTabStop) {
request = gcnew TraversalRequest(FocusNavigationDirection::Next);
}
if (request != nullptr)
return ((IKeyboardInputSink^) this)->KeyboardInputSite->OnNoMoreTabStops(request);
}
// Only call IsDialogMessage for keys it will do something with.
if (msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYDOWN) {
switch (m.wParam) {
case VK_TAB:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_EXECUTE:
case VK_RETURN:
case VK_ESCAPE:
case VK_CANCEL:
IsDialogMessage(dialog, &m);
// IsDialogMessage should be called ProcessDialogMessage --
// it processes messages without ever really telling you
// if it handled a specific message or not
return true;
}
}
return false; // not a key we handled
}
這在一個部分有許多程式碼,因此可以使用一些更詳細的說明。 首先,使用 C++ 和 C++ 宏的程式碼;您必須注意,已經有名為 TranslateAccelerator
的宏,其定義于 winuser.h:
#define TranslateAccelerator TranslateAcceleratorW
因此,請務必定義 TranslateAccelerator
方法,而不是 TranslateAcceleratorW
方法。
同樣地,還有 Unmanaged winuser.h MSG 和 Managed Microsoft::Win32::MSG
結構。 您可以使用 C++ ::
運算子來厘清這兩者。
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
ModifierKeys modifiers) override
{
::MSG m = ConvertMessage(msg);
}
這兩個 MSG 都有相同的資料,但有時候使用 Unmanaged 定義會比較容易,因此在此範例中,您可以定義明顯的轉換常式:
::MSG ConvertMessage(System::Windows::Interop::MSG% msg) {
::MSG m;
m.hwnd = (HWND) msg.hwnd.ToPointer();
m.lParam = (LPARAM) msg.lParam.ToPointer();
m.message = msg.message;
m.wParam = (WPARAM) msg.wParam.ToPointer();
m.time = msg.time;
POINT pt;
pt.x = msg.pt_x;
pt.y = msg.pt_y;
m.pt = pt;
return m;
}
回到 TranslateAccelerator
。 基本原則是呼叫 Win32 函 IsDialogMessage
式以盡可能多地執行工作,但 IsDialogMessage
無法存取對話方塊以外的任何專案。 身為對話周圍的使用者索引標籤,當 Tabbing 在對話中超過最後一個控制項時,您必須呼叫 IKeyboardInputSite::OnNoMoreStops
來將焦點設定為 WPF 部分。
// Win32's IsDialogMessage() will handle most of the tabbing, but doesn't know
// what to do when it reaches the last tab stop
if (m.message == WM_KEYDOWN && m.wParam == VK_TAB) {
HWND firstTabStop = GetDlgItem(dialog, IDC_EDIT1);
HWND lastTabStop = GetDlgItem(dialog, IDCANCEL);
TraversalRequest^ request = nullptr;
if (GetKeyState(VK_SHIFT) && GetFocus() == firstTabStop) {
request = gcnew TraversalRequest(FocusNavigationDirection::Last);
}
else if (!GetKeyState(VK_SHIFT) && GetFocus() == lastTabStop) { {
request = gcnew TraversalRequest(FocusNavigationDirection::Next);
}
if (request != nullptr)
return ((IKeyboardInputSink^) this)->KeyboardInputSite->OnNoMoreTabStops(request);
}
最後,請呼叫 IsDialogMessage
。 但方法的其中一項 TranslateAccelerator
責任是告訴 WPF 您是否處理了擊鍵。 如果您未處理它,輸入事件可以透過應用程式的其餘部分進行通道和泡泡。 在這裡,您將公開鍵盤混亂處理和 Win32 中輸入架構本質的古怪。 不幸的是, IsDialogMessage
不會以任何方式傳回它是否處理特定的擊鍵。 更糟的是,它會在擊鍵時呼叫 DispatchMessage()
它不應該處理! 因此,您必須進行反向工程 IsDialogMessage
,並只針對您知道金鑰的金鑰呼叫它將會處理:
// Only call IsDialogMessage for keys it will do something with.
if (msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYDOWN) {
switch (m.wParam) {
case VK_TAB:
case VK_LEFT:
case VK_UP:
case VK_RIGHT:
case VK_DOWN:
case VK_EXECUTE:
case VK_RETURN:
case VK_ESCAPE:
case VK_CANCEL:
IsDialogMessage(dialog, &m);
// IsDialogMessage should be called ProcessDialogMessage --
// it processes messages without ever really telling you
// if it handled a specific message or not
return true;
}
覆寫 TabInto 方法以支援 Tabbing
既然您已實作 TranslateAccelerator
,使用者就可以在對話方塊內四處定位,並將它從中定位到較大的 WPF 應用程式。 但使用者無法將索引標籤回到對話方塊中。 若要解決此問題,請覆寫 TabInto
:
public:
virtual bool TabInto(TraversalRequest^ request) override {
if (request->FocusNavigationDirection == FocusNavigationDirection::Last) {
HWND lastTabStop = GetDlgItem(dialog, IDCANCEL);
SetFocus(lastTabStop);
}
else {
HWND firstTabStop = GetDlgItem(dialog, IDC_EDIT1);
SetFocus(firstTabStop);
}
return true;
}
參數會告訴您索引 TraversalRequest
標籤動作是索引標籤或移位索引標籤。
覆寫 OnMnemonic 方法以支援助憶鍵
鍵盤處理幾乎完成,但有一件事遺漏 – 助憶鍵無法運作。 如果使用者按下 alt-F,焦點不會跳到 [名字:] 編輯方塊。 因此,您會覆寫 OnMnemonic
方法:
virtual bool OnMnemonic(System::Windows::Interop::MSG% msg, ModifierKeys modifiers) override {
::MSG m = ConvertMessage(msg);
// If it's one of our mnemonics, set focus to the appropriate hwnd
if (msg.message == WM_SYSCHAR && GetKeyState(VK_MENU /*alt*/)) {
int dialogitem = 9999;
switch (m.wParam) {
case 's': dialogitem = IDOK; break;
case 'c': dialogitem = IDCANCEL; break;
case 'f': dialogitem = IDC_EDIT1; break;
case 'l': dialogitem = IDC_EDIT2; break;
case 'p': dialogitem = IDC_EDIT3; break;
case 'a': dialogitem = IDC_EDIT4; break;
case 'i': dialogitem = IDC_EDIT5; break;
case 't': dialogitem = IDC_EDIT6; break;
case 'z': dialogitem = IDC_EDIT7; break;
}
if (dialogitem != 9999) {
HWND hwnd = GetDlgItem(dialog, dialogitem);
SetFocus(hwnd);
return true;
}
}
return false; // key unhandled
};
為什麼不在這裡打電話 IsDialogMessage
? 您有與之前相同的問題,您必須能夠通知 WPF 程式碼是否處理按鍵,而且 IsDialogMessage
無法執行此動作。 另外還有第二個問題,因為 IsDialogMessage
如果焦點 HWND 不在對話方塊中,拒絕處理助憶鍵。
具現化 HwndHost 衍生類別
最後,現在所有索引鍵和索引標籤支援都已就緒,您可以將 您 HwndHost 放入較大的 WPF 應用程式。 如果主要應用程式是以 XAML 撰寫,將它放在正確位置的最簡單方式是讓您想要放置 HwndHost 的空白 Border 元素。 在這裡,您會建立 Border 名為 insertHwndHostHere
的 :
<Window x:Class="WPFApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Windows Presentation Framework Application"
Loaded="Window1_Loaded"
>
<StackPanel>
<Button Content="WPF button"/>
<Border Name="insertHwndHostHere" Height="200" Width="500"/>
<Button Content="WPF button"/>
</StackPanel>
</Window>
接著,剩下的就是在程式碼序列中尋找一個很好的位置,以具現化 HwndHost ,並將它 Border 連接到 。 在此範例中,您會將它放在衍生類別的 Window 建構函式中:
public partial class Window1 : Window {
public Window1() {
}
void Window1_Loaded(object sender, RoutedEventArgs e) {
HwndHost host = new ManagedCpp.MyHwndHost();
insertHwndHostHere.Child = host;
}
}
這會為您提供:
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應