[前提条件]
「WPF と Win32 相互運用」を参照してください。
Windows Presentation Framework (HwndHost) 内での Win32 のチュートリアル
WPF アプリケーション内で Win32 コンテンツを再利用するには、を使用します。これは、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 をマネージドとしてコンパイルする (
/clr
)ダイアログをコントロールに変換する
HwndHostメソッドと
BuildWindowCore
メソッドを使用してDestroyWindowCore
の派生クラスを定義するダイアログ キー
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
2 行目を次に変更します。
STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL
このアクションでは、自己完結型コントロールに完全にはパッケージ化されません。Win32 が特定のメッセージを処理できるように IsDialogMessage()
を呼び出す必要がありますが、コントロールの変更により、これらのコントロールを別の 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 内で呼び出される最初のメソッドの 1 つであるため、後で定義する関数を呼び出して、 InitializeGlobals()
と呼ばれる標準的な Win32 初期化も行う必要があります。
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
}
これは 1 つの部分に含まれる多くのコードであるため、さらに詳細な説明を使用できます。 まず、C++ マクロと C++ マクロを使用するコード。 TranslateAccelerator
という名前のマクロが既に存在し、winuser.h で定義されていることに注意する必要があります。
#define TranslateAccelerator TranslateAcceleratorW
そのため、TranslateAccelerator
メソッドではなく、TranslateAcceleratorW
メソッドを定義してください。
同様に、アンマネージド winuser.h MSG とマネージド Microsoft::Win32::MSG
構造体の両方があります。 C++ ::
演算子を使用して、2 つの間であいまいさを解消できます。
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
ModifierKeys modifiers) override
{
::MSG m = ConvertMessage(msg);
}
どちらの MSG も同じデータを持っていますが、アンマネージ定義を使用する方が簡単な場合があるため、このサンプルでは明らかな変換ルーチンを定義できます。
::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
はダイアログの外部にはアクセスできません。 ダイアログのユーザー タブとして、ダイアログの最後のコントロールを超えてタブ移動を実行する場合は、 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
メソッドの役割の 1 つは、キーストロークを処理したかどうかを 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 メソッドをオーバーライドしてタブ処理をサポートする
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 メソッドをオーバーライドしてニーモニックをサポートする
キーボードの処理はほぼ完了していますが、ニーモニックが機能しないという 1 つの問題があります。 ユーザーが 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
。 また、フォーカスされた HWND がダイアログ ボックス内にない場合、 IsDialogMessage
がニーモニックの処理を拒否するため、2 つ目の問題もあります。
HwndHost 派生クラスをインスタンス化する
最後に、すべてのキーとタブのサポートが整ったので、 HwndHost を大規模な WPF アプリケーションに配置できます。 メイン アプリケーションが XAML で記述されている場合、適切な場所に配置する最も簡単な方法は、Borderを配置する空のHwndHost要素のままにすることです。 ここでは、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;
}
}
次のものが得られます。
こちらも参照ください
.NET Desktop feedback