Sdílet prostřednictvím


Hostování obsahu Win32 v platformě WPF

Předpoklady

Viz interoperabilita WPF a Win32.

Návod win32 inside Windows Presentation Framework (HwndHost)

Chcete-li znovu použít obsah Win32 uvnitř aplikací WPF, použijte HwndHost, což je ovládací prvek, který dělá HWND vypadat jako WPF obsah. Podobně jako HwndSource, HwndHost je jednoduché použít: odvozovat a HwndHost implementovat BuildWindowCore a DestroyWindowCore metody, pak vytvořit instanci HwndHost odvozené třídy a umístit ji do vaší aplikace WPF.

Pokud je vaše logika Win32 již zabalena jako ovládací prvek, implementace BuildWindowCore je o něco více 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 sadě Visual Studio a C++, i když je to také možné provést v jiném jazyce nebo na příkazovém řá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

  • Definování odvozené třídy HwndHost pomocí BuildWindowCore a DestroyWindowCore metod

  • Metoda přepsání TranslateAccelerator pro zpracování dialogových klíčů

  • Přepsání TabInto metody pro podporu tabbingu

  • Metoda přepsání OnMnemonic pro podporu mnemonics

  • Vytvořte instanci HwndHost podtřídy a umístěte ji 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

Naimportujte 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 BuildWindowCore a DestroyWindowCore metody:

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íčů 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 metodu TranslateAccelerator , nikoli metodu TranslateAcceleratorW .

Podobně existuje nespravovaný winuser.h MSG i spravovaná Microsoft::Win32::MSG struktura. Mezi nimi můžete nejednoznačit pomocí operátoru 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 volání funkce IsDialogMessage Win32 pro co nejvíce práce, ale IsDialogMessage nemá přístup k ničemu mimo dialogové okno. Jako karta uživatele kolem dialogového okna při spuštění tabulátoru po posledním ovládacím prvku v našem dialogovém okně musíte nastavit fokus na část WPF volá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í TranslateAccelerator metody je, že WPF říká, jestli jste zvládli stisknutí kláves nebo ne. Pokud jste ji nezpracovali, vstupní událost může tunelovat a bublinovat přes zbytek aplikace. V této části zveřejníte přehled o manipulaci s klávesnicí a povaze vstupní architektury v systému Win32. IsDialogMessage Bohužel se nevrací žádným způsobem, zda zpracovává konkrétní stisknutí klávesy. Ještě horší je, že zavolá DispatchMessage() stisknutí kláves by nemělo zvládnout! 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 v dialogovém okně přechádět tabulátorem a vysunutou kartu do větší aplikace WPF. Uživatel se ale nemůže vrátit zpět do dialogového okna. Chcete-li to 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, jestli je akce tabulátoru tabulátoru nebo karta shift.

Přepsání metody OnMnemonic pro podporu Mnemonics

Zpracování klávesnice je téměř dokončené, ale chybí jedna věc – mimonics nefungují. Pokud uživatel stiskne alt-F, fokus nepřeskočí do textového pole Jméno:. Proto 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ávesy nebo ne, a IsDialogMessage nemůže to udělat. Existuje také druhý problém, protože IsDialogMessage odmítá zpracovat měmonic, pokud fokus HWND není uvnitř dialogového okna.

Vytvoření instance odvozené třídy HwndHost

A konečně, když je teď všechna podpora kláves a tabulátoru na místě, můžete ji umístit HwndHost do větší aplikace WPF. Pokud je hlavní aplikace napsaná v XAML, nejjednodušší způsob, jak ji umístit na správné místo, je ponechat prázdný Border prvek, kam chcete vložit HwndHost. Tady vytvoříte pojmenovanouinsertHwndHostHere:Border

<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>

Pak vše, co zbývá, je najít dobré místo v posloupnosti HwndHost kódu vytvořit instanci a připojit ho Borderk . V tomto příkladu ho vložíte do konstruktoru pro odvozenou Window třídu:

public partial class Window1 : Window {
    public Window1() {
    }

    void Window1_Loaded(object sender, RoutedEventArgs e) {
        HwndHost host = new ManagedCpp.MyHwndHost();
        insertHwndHostHere.Child = host;
    }
}

To vám dává:

Screenshot of the WPF app running.

Viz také