Hosting zawartości Win32 w WPF
Wymagania wstępne
Zobacz Współdziałanie WPF i Win32.
Przewodnik po systemie Win32 inside Windows Presentation Framework (HwndHost)
Aby ponownie użyć zawartości Win32 w aplikacjach WPF, użyj HwndHostmetody , która jest kontrolką, która sprawia, że HWNDs wyglądają jak zawartość WPF. Podobnie jak HwndSourcew przypadku metody , HwndHost można łatwo użyć: pochodzą z HwndHost metody i BuildWindowCore
i DestroyWindowCore
są tworzone, a następnie tworzy wystąpienie HwndHost klasy pochodnej i umieszczaj ją w aplikacji WPF.
Jeśli logika Win32 jest już spakowana jako kontrolka, BuildWindowCore
implementacja jest nieco większa niż wywołanie metody CreateWindow
. Aby na przykład utworzyć kontrolkę Win32 LISTBOX w języku C++:
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
}
Załóżmy jednak, że kod Win32 nie jest tak samodzielny? Jeśli tak, możesz utworzyć okno dialogowe Win32 i osadzić jego zawartość w większej aplikacji WPF. W przykładzie pokazano to w programach Visual Studio i C++, chociaż można to zrobić również w innym języku lub w wierszu polecenia.
Zacznij od prostego okna dialogowego, które jest kompilowane w projekcie dll języka C++.
Następnie wprowadź okno dialogowe w większej aplikacji WPF:
Kompilowanie biblioteki DLL jako zarządzanej (
/clr
)Przekształcanie okna dialogowego w kontrolkę
Definiowanie klasy pochodnej klasy HwndHost z metodami
BuildWindowCore
iDestroyWindowCore
Zastąpij
TranslateAccelerator
metodę do obsługi kluczy okien dialogowychZastępowanie
TabInto
metody do obsługi tabulacjiZastępowanie
OnMnemonic
metody do obsługi mnemonicsUtwórz wystąpienie podklasy HwndHost i umieść ją pod odpowiednim elementem WPF
Przekształcanie okna dialogowego w kontrolkę
Okno dialogowe można przekształcić w podrzędny HWND przy użyciu stylów WS_CHILD i DS_CONTROL. Przejdź do pliku zasobu (rc), w którym zdefiniowano okno dialogowe, i znajdź początek definicji okna dialogowego:
IDD_DIALOG1 DIALOGEX 0, 0, 303, 121
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
Zmień drugi wiersz na:
STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL
Ta akcja nie w pełni pakuje jej do samodzielnej kontrolki; Nadal trzeba wywołać metodę IsDialogMessage()
, aby system Win32 mógł przetwarzać pewne komunikaty, ale zmiana kontrolki zapewnia prosty sposób umieszczania tych kontrolek wewnątrz innego HWND.
Podklasa HwndHost
Zaimportuj następujące przestrzenie nazw:
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;
Następnie utwórz klasę pochodną i HwndHost zastąp BuildWindowCore
metody i :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
}
W tym miejscu użyjesz polecenia CreateDialog
, aby utworzyć okno dialogowe, które jest naprawdę kontrolką. Ponieważ jest to jedna z pierwszych metod wywoływanych wewnątrz biblioteki DLL, należy również wykonać niektóre standardowe inicjowanie Win32, wywołując funkcję, którą zdefiniujesz później, o nazwie 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);
Zastąp metodę TranslateAccelerator do obsługi kluczy dialogowych
Jeśli teraz uruchomisz ten przykład, zostanie wyświetlona kontrolka okna dialogowego, ale zignoruje wszystkie przetwarzanie klawiatury, które sprawia, że okno dialogowe jest funkcjonalne okno dialogowe. Teraz należy zastąpić implementację TranslateAccelerator
(która pochodzi z IKeyboardInputSink
interfejsu HwndHost implementujący). Ta metoda jest wywoływana, gdy aplikacja odbiera WM_KEYDOWN i 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
}
Jest to dużo kodu w jednym kawałku, więc może użyć bardziej szczegółowych wyjaśnień. Najpierw kod korzystający z makr C++ i C++; Należy pamiętać, że istnieje już makro o nazwie TranslateAccelerator
, które jest zdefiniowane w pliku winuser.h:
#define TranslateAccelerator TranslateAcceleratorW
Dlatego należy zdefiniować metodę TranslateAccelerator
, a nie metodę TranslateAcceleratorW
.
Podobnie istnieje zarówno niezarządzana grupa msg winuser.h, jak i zarządzana Microsoft::Win32::MSG
struktura. Można uściślać między nimi przy użyciu operatora C++ ::
.
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
ModifierKeys modifiers) override
{
::MSG m = ConvertMessage(msg);
}
Obie grupy msg mają te same dane, ale czasami łatwiej jest pracować z definicją niezarządzaną, więc w tym przykładzie można zdefiniować oczywistą procedurę konwersji:
::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;
}
Wróć do TranslateAccelerator
. Podstawową zasadą jest wywołanie funkcji IsDialogMessage
Win32, aby wykonać jak najwięcej pracy, ale IsDialogMessage
nie ma dostępu do czegokolwiek spoza okna dialogowego. W przypadku karty użytkownika okna dialogowego, gdy tabulatory są uruchamiane obok ostatniej kontrolki w naszym oknie dialogowym, musisz ustawić fokus na część WPF przez wywołanie metody IKeyboardInputSite::OnNoMoreStops
.
// 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);
}
Na koniec wywołaj metodę IsDialogMessage
. Ale jedną z obowiązków TranslateAccelerator
metody jest mówi WPF, czy obsłużyłeś naciśnięcie klawiszy, czy nie. Jeśli go nie obsłużysz, zdarzenie wejściowe może tunelować i bąbelkować przez pozostałą część aplikacji. Tutaj uwidocznisz dziwactwa obsługi messange klawiatury i charakter architektury wejściowej w Win32. Niestety, nie zwraca w żaden sposób, IsDialogMessage
czy obsługuje określone naciśnięcie klawisza. Co gorsza, wywoła DispatchMessage()
naciśnięć klawiszy, których nie powinien obsługiwać! Dlatego trzeba będzie odwrócić inżyniera IsDialogMessage
i wywołać go tylko dla kluczy, które wiesz, że będzie obsługiwać:
// 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;
}
Zastąpij metodę TabInto do obsługi tabbingu
Teraz, po zaimplementowaniu TranslateAccelerator
, użytkownik może obejść się wewnątrz okna dialogowego i na karcie z niej do większej aplikacji WPF. Użytkownik nie może jednak wrócić do okna dialogowego. Aby rozwiązać ten problem, należy zastąpić TabInto
polecenie :
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;
}
Parametr TraversalRequest
informuje, czy akcja karty jest kartą, czy tabulatorem.
Zastąpij metodę OnMnemonic do obsługi Mnemonics
Obsługa klawiatury jest prawie kompletna, ale brakuje jednej rzeczy — mnemonics nie działają. Jeśli użytkownik naciska klawisze alt-F, fokus nie przechodzi do pola edycji "Imię:". Dlatego zastąpisz metodę 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
};
Dlaczego nie dzwonić IsDialogMessage
tutaj? Masz ten sam problem co wcześniej — musisz mieć możliwość informowania kodu WPF o tym, czy kod obsłużył naciśnięcie klawiszy, czy nie, i IsDialogMessage
nie może tego zrobić. Istnieje również drugi problem, ponieważ IsDialogMessage
odmawia przetwarzania mnemonic, jeśli skoncentrowany HWND nie znajduje się wewnątrz okna dialogowego.
Utworzenie wystąpienia klasy pochodnej HwndHost
Na koniec, teraz, gdy jest dostępna obsługa wszystkich klawiszy i kart, możesz umieścić plik HwndHost w większej aplikacji WPF. Jeśli główna aplikacja jest napisana w języku XAML, najprostszym sposobem, aby umieścić ją w odpowiednim miejscu, jest pozostawienie pustego Border elementu, w którym chcesz umieścić HwndHostelement . W tym miejscu utworzysz Border nazwę 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>
Następnie wystarczy znaleźć dobre miejsce w sekwencji kodu, aby utworzyć wystąpienie HwndHost obiektu i połączyć go z elementem Border. W tym przykładzie umieścisz go wewnątrz konstruktora dla klasy pochodnej Window :
public partial class Window1 : Window {
public Window1() {
}
void Window1_Loaded(object sender, RoutedEventArgs e) {
HwndHost host = new ManagedCpp.MyHwndHost();
insertHwndHostHere.Child = host;
}
}
Co daje:
Zobacz też
.NET Desktop feedback