Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Požadavky
Podívejte se na WPF a Win32 interoperabilitu.
Průvodce Win32 uvnitř Windows Presentation Framework (HwndHost)
Chcete-li znovu použít obsah Win32 v aplikacích WPF, použijte HwndHost, což je ovládací prvek, který dělá HWND vypadat jako obsah WPF. Stejně jako HwndSourceje HwndHost jednoduché použít: odvozovat z HwndHost a implementovat BuildWindowCore a DestroyWindowCore metody, pak vytvořit instanci HwndHost odvozené třídy a umístit ji do aplikace WPF.
Pokud je vaše logika Win32 již zabalena jako ovládací prvek, implementace BuildWindowCore je o málo víc než volání CreateWindow. Například vytvoření ovládacího prvku Win32 LISTBOX v jazyce 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
}
Ale předpokládejme, že kód Win32 není tak samostatný? Pokud ano, můžete vytvořit dialogové okno Win32 a vložit jeho obsah do větší aplikace WPF. Ukázka to ukazuje v prostředí Visual Studio a C++, ale je také možné to provést v jiném jazyce nebo pomocí příkazového řádku.
Začněte jednoduchým dialogem, který je zkompilován do projektu knihovny DLL jazyka C++.
V dalším kroku zaveděte dialogové okno do větší aplikace WPF:
Zkompilujte knihovnu DLL jako spravovanou (
/clr)Převod dialogového okna na ovládací prvek
Definujte odvozenou třídu HwndHost pomocí metod
BuildWindowCoreaDestroyWindowCorePřepsání metody
TranslateAcceleratorpro zpracování klíčů dialogového oknaPřepis metody
TabIntopro podporu tabulátoruPřepsání
OnMnemonicmetody pro podporu mnemonicsVytvoření instance podtřídy HwndHost a jeho umístění pod správný prvek WPF
Převod dialogového okna na ovládací prvek
Dialogové okno můžete převést na podřízený HWND pomocí WS_CHILD a DS_CONTROL stylů. Přejděte do souboru prostředků (.rc), kde je dialogové okno definované, a najděte začátek definice dialogového okna:
IDD_DIALOG1 DIALOGEX 0, 0, 303, 121
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
Změňte druhý řádek na:
STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL
Tato akce ji plně nezabalí do samostatného ovládacího prvku; stále potřebujete volat IsDialogMessage(), aby Win32 mohl zpracovávat určité zprávy, ale změna ovládacího prvku poskytuje jednoduchý způsob, jak tyto ovládací prvky umístit do jiného HWND.
Podtřída HwndHost
Importujte následující obory názvů:
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;
Pak vytvořte odvozenou třídu HwndHost a přepište metody BuildWindowCore a 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
}
Tady použijete CreateDialog k vytvoření dialogového okna, které je opravdu ovládací prvek. Vzhledem k tomu, že se jedná o jednu z prvních metod volaných uvnitř knihovny DLL, měli byste také provést některé standardní inicializace Win32 voláním funkce, kterou definujete později, která se nazývá 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);
Přepsání metody TranslateAccelerator pro zpracování kláves dialogového okna
Pokud jste teď spustili tuto ukázku, získáte ovládací prvek dialogového okna, který se zobrazí, ale bude ignorovat veškeré zpracování klávesnice, které z dialogového okna vytvoří funkční dialogové okno. Teď byste měli přepsat implementaci TranslateAccelerator (která pochází z IKeyboardInputSink, rozhraní, které HwndHost implementuje). Tato metoda se volá, když aplikace obdrží WM_KEYDOWN a 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
}
To je hodně kódu v jedné části, takže by mohlo použít podrobnější vysvětlení. Nejprve kód pomocí maker C++ a C++; musíte vědět, že již existuje makro s názvem TranslateAccelerator, které je definováno v winuser.h:
#define TranslateAccelerator TranslateAcceleratorW
Proto nezapomeňte definovat TranslateAccelerator metodu, nikoli metodu TranslateAcceleratorW.
Podobně existuje jak nespravovaný winuser.h MSG, tak spravovaná Microsoft::Win32::MSG struktura. Můžete rozeznat mezi těmito dvěma pomocí operátoru :: v jazyce C++.
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
ModifierKeys modifiers) override
{
::MSG m = ConvertMessage(msg);
}
Obě skupiny msg mají stejná data, ale někdy je jednodušší pracovat s nespravovanou definicí, takže v této ukázce můžete definovat zřejmé rutiny převodu:
::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;
}
Zpět na TranslateAccelerator. Základním principem je zavolat funkci Win32 IsDialogMessage, aby se vykonalo co nejvíce práce, ale IsDialogMessage nemá přístup k ničemu mimo dialogové okno. Při navigaci pomocí tabulátoru v dialogovém okně, pokud tabulátor projde za poslední ovládací prvek v dialogu, je třeba nastavit fokus na část WPF zavoláním 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);
}
Nakonec zavolejte IsDialogMessage. Ale jednou z zodpovědností metody TranslateAccelerator je, že WPF říká, jestli jste zvládli stisknutí klávesy, nebo ne. Pokud jste ji nezpracovali, vstupní událost může tunelovat a bublinovat přes zbytek aplikace. Poodhalíte zde zvláštnost v zacházení se zprávami z klávesnice a povahu vstupní architektury v prostředí Win32.
IsDialogMessage bohužel nevrací žádným způsobem, zda zpracovává konkrétní stisknutí klávesy. Ještě horší je, že bude volat DispatchMessage() na stisknutí kláves, které by nemělo zpracovávat! Takže budete muset provést zpětnou analýzu IsDialogMessagea volat ho pouze pro klíče, které víte, že bude zpracovávat:
// 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;
}
Přepsání metody TabInto pro podporu tabbingu
Teď, když jste implementovali TranslateAccelerator, může uživatel pomocí klávesy Tab se v dialogovém okně přesouvat a opustit jej do zbytku aplikace WPF. Uživatel se ale nemůže vrátit zpět do dialogového okna. Pokud chcete tento problém vyřešit, přepíšete 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;
}
Parametr TraversalRequest vám řekne, zda se jedná o akci tabulátoru nebo kombinaci Shift+Tab.
Přepsání metody OnMnemonic pro podporu mnemonik
Zpracování klávesnice je téměř dokončené, ale chybí jedna věc – klávesové zkratky nefungují. Pokud uživatel stiskne alt-F, kurzor nepřeskočí do textového pole Křestní jméno:. Proto tedy přepíšete metodu 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
};
Proč tady nevolat IsDialogMessage? Máte stejný problém jako předtím– musíte být schopni informovat kód WPF, jestli váš kód zpracovával stisknutí kláves nebo ne, a IsDialogMessage to nemůže udělat. Existuje také druhý problém, protože IsDialogMessage odmítá zpracovat mnemonic, pokud okno s fokusem HWND není uvnitř dialogového okna.
Vytvoření instance odvozené třídy HwndHost
Nakonec, když je veškerá podpora kláves a záložek na místě, můžete své HwndHost umístit do větší aplikace WPF. Pokud je hlavní aplikace napsaná v JAZYCE XAML, nejjednodušší způsob, jak ji umístit na správné místo, je ponechat prázdný Border prvek, kam chcete umístit HwndHost. Tady vytvoříte Border s názvem 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>
Jediné, co zbývá, je najít dobré místo v posloupnosti kódu, kde se vytvoří instance HwndHost a připojí se k Border. V tomto příkladu ho vložíte do konstruktoru pro odvozenou třídu Window:
public partial class Window1 : Window {
public Window1() {
}
void Window1_Loaded(object sender, RoutedEventArgs e) {
HwndHost host = new ManagedCpp.MyHwndHost();
insertHwndHostHere.Child = host;
}
}
Což vám dává:
Viz také
.NET Desktop feedback