Partilhar via


Hospedagem de conteúdo do Win32 no WPF

Pré-requisitos

See WPF e a interoperação de Win32.

A Walkthrough of Win32 Inside Windows Presentation Framework (HwndHost)

To reuse Win32 content inside WPF applications, use HwndHost, which is a control that makes HWNDs look like WPF content. Como HwndSource, HwndHost é simples de usar: derivar de HwndHost e implementar BuildWindowCore e DestroyWindowCore métodos, em seguida, instanciar seu HwndHost classe derivada e inseri-lo no seu WPF aplicativo.

Se sua Win32 lógica já é empacotada como um controle, o BuildWindowCore implementação é um pouco mais do que uma chamada para CreateWindow. For example, to create a Win32 LISTBOX control in 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
}

But suppose the Win32 code is not quite so self-contained? Se assim, você pode criar um Win32 caixa de diálogo caixa e incorporar o seu conteúdo em uma maior WPF aplicativo. The sample shows this in Microsoft Visual Studio and C++, although it is also possible to do this in a different language or at the command line.

Start with a simple dialog, which is compiled into a C++ DLL project.

Next, introduce the dialog into the larger WPF application:

  • Compile the DLL as managed (/clr)

  • Turn the dialog into a control

  • Define the derived class of HwndHost with BuildWindowCore and DestroyWindowCore methods

  • Override TranslateAccelerator method to handle dialog keys

  • Substituir TabInto método para dar suporte às guias

  • Override OnMnemonic method to support mnemonics

  • Instantiate the HwndHost subclass and put it under the right WPF element

Turn the Dialog into a Control

You can turn a dialog box into a child HWND using the WS_CHILD and DS_CONTROL styles. Go into the resource file (.rc) where the dialog is defined, and find the beginning of the definition of the dialog:

IDD_DIALOG1 DIALOGEX 0, 0, 303, 121
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU

Change the second line to:

STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL

This action does not fully package it into a self-contained control; you still need to call IsDialogMessage() so Win32 can process certain messages, but the control change does provide a straightforward way of putting those controls inside another HWND.

Subclass HwndHost

Import the following namespaces:

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;

Then create a derived class of HwndHost and override the BuildWindowCore and DestroyWindowCore methods:

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
        }

Here you use the CreateDialog to create the dialog box that is really a control. Since this is one of the first methods called inside the DLL, you should also do some standard Win32 initialization by calling a function you will define later, called 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);

Override TranslateAccelerator Method to Handle Dialog Keys

If you ran this sample now, you would get a dialog control that displays, but it would ignore all of the keyboard processing that makes a dialog box a functional dialog box. You should now override the TranslateAccelerator implementation (which comes from IKeyboardInputSink, an interface that HwndHost implements). This method gets called when the application receives WM_KEYDOWN and 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
        }

This is a lot of code in one piece, so it could use some more detailed explanations. First, the code using C++ and C++ macros; you need to be aware that there is already a macro named TranslateAccelerator, which is defined in winuser.h:

#define TranslateAccelerator  TranslateAcceleratorW

So make sure to define a TranslateAccelerator method and not a TranslateAcceleratorW method.

Similarly, there is both the unmanaged winuser.h MSG and the managed Microsoft::Win32::MSG struct. You can disambiguate between the two using the C++ :: operator.

virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg, 
    ModifierKeys modifiers) override 
{
    ::MSG m = ConvertMessage(msg);

Both MSGs have the same data, but sometimes it is easier to work with the unmanaged definition, so in this sample you can define the obvious conversion routine:

::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;
}

Back to TranslateAccelerator. O princípio básico é chamar o Win32 função IsDialogMessage fazer quanto trabalho possível, mas IsDialogMessage não tem acesso a algo fora da caixa de diálogo. Como uma guia do usuário ao redor da caixa de diálogo, quando a tabulação é executado após o último controle na nossa caixa de diálogo, você precisa definir o foco para o WPF parte chamando 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);
}

Finally, call IsDialogMessage. But one of the responsibilities of a TranslateAccelerator method is telling WPF whether you handled the keystroke or not. If you did not handle it, the input event can tunnel and bubble through the rest of the application. Aqui, você irá expor uma sutileza de manipulação de messange de teclado e a natureza da arquitetura de entrada em Win32. Infelizmente, IsDialogMessage não retornar de forma alguma se lida com um pressionamento de tecla específico. Even worse, it will call DispatchMessage() on keystrokes it should not handle! Assim você terá a engenharia reversa IsDialogMessagee chamar apenas para as teclas que você sabe que ele manipulará:

// 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;
    }

Override TabInto Method to Support Tabbing

Agora que você implementou TranslateAccelerator, tab em torno de um usuário dentro da caixa de diálogo caixa e guia fora dele em maior WPF aplicativo. But a user cannot tab back into the dialog box. To solve that, you override 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;
    }

O TraversalRequest parâmetro informa se a ação de guia é um guia de tab ou shift.

Override OnMnemonic Method to Support Mnemonics

Keyboard handling is almost complete, but there is one thing missing – mnemonics do not work. Se um usuário pressiona alt-F, doe de foco não saltar para o "nome:" caixa de edição. So, you override the OnMnemonic method:

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

Why not call IsDialogMessage here? Você tem o mesmo problema como antes - você precisa ser capaz de informar WPF de código se o seu código tratado o pressionamento de teclas ou não, e IsDialogMessage não pode fazer isso. There is also a second issue, because IsDialogMessage refuses to process the mnemonic if the focused HWND is not inside the dialog box.

Instantiate the HwndHost Derived Class

Finalmente, agora que todo o suporte de chave e o guia está no lugar, você pode colocar seu HwndHost em maior WPF aplicativo. If the main application is written in XAML, the easiest way to put it in the right place is to leave an empty Border element where you want to put the HwndHost. Here you create a Border named insertHwndHostHere:

<Window x:Class="WPFApplication1.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://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>

Then all that remains is to find a good place in code sequence to instantiate the HwndHost and connect it to the Border. In this example, you will put it inside the constructor for the Window derived class:

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

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

Which gives you:

Captura de tela de aplicativo HTML

Consulte também

Conceitos

WPF e a interoperação de Win32