How to use 'Back' & 'Forward' navigation button events in WPF WebBrowser?

Viewed 6380

The WebBrowser control in WPF is inherited from theUIElement, but we cannot register event handlers in UIElement events. Why is it? At WPF WebBrowser Mouse Events not working as expected, it is answered but I still cannot understand.

Anyway, hooking up handlers to the events provided by the document of the WebBrowser can catch most mouse events but cannot use 'Back' & 'Forward' navigation button events. Since the internet explorer can do this, I think it is possible. Is there any way to solve this issue?

UPDATE: In this question, 'Back' & 'Forward' navigation buttonsmean XButton1 and XButton2 in 5-button mouse system.

UPDATE2: I fixed this question with the Navid Rahmani's answer. I'd think someone will need this answer, so I attach main part. If finding any problem or more reasonable solution, please let me know.

    //This code assumes the `WebBrowser` field named _webBrowser is already initiated.
    //For the detail out of this code, please refer to the Navid Rahmani's answer.

    private bool _isMouseOver;
    private HTMLDocumentEvents2_Event _docEvent;    

    public ctor()
    {
        _webBrowser.LoadCompleted += _webBrowser_LoadCompleted;
    }

    private void _webBrowser_LoadCompleted(object sender, NavigationEventArgs e)
    {
        if (_docEvent != null)
        {
            _docEvent.onmouseover -= _docEvent_onmouseover;
            _docEvent.onmouseout -= _docEvent_onmouseout;
        }
        if (_webBrowser.Document != null)
        {
            _docEvent = (HTMLDocumentEvents2_Event)_webBrowser.Document;
            _docEvent.onmouseover += _docEvent_onmouseover;
            _docEvent.onmouseout += _docEvent_onmouseout;
        }
    }

    void _docEvent_onmouseout(IHTMLEventObj pEvtObj)
    {
        _isMouseOver = false;
    }

    void _docEvent_onmouseover(IHTMLEventObj pEvtObj)
    {
        _isMouseOver = true;
    }


    private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (_isMouseOver)
        {
            if (nCode >= 0 && (MouseMessages)wParam == MouseMessages.XBUTTON)
            {
                var hookStruct = (Msllhookstruct)Marshal.PtrToStructure(lParam, typeof(Msllhookstruct));
                if (hookStruct.mouseData == 0x10000)
                {
                    //do something when XButto1 clicked
                }
                else if (hookStruct.mouseData == 0x20000)
                {
                    //do something when XButto2 clicked
                }
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }


    private enum MouseMessages
    {
        //WM_LBUTTONDOWN = 0x00A1,
        //WM_LBUTTONUP = 0x0202,
        //WM_MOUSEMOVE = 0x0200,
        //WM_MOUSEWHEEL = 0x020A,
        //WM_RBUTTONDOWN = 0x0204,
        //WM_RBUTTONUP = 0x0205,
        XBUTTON = 0x020B,
    }
4 Answers

TL;DR

Install my nuget package Lette.Wpf.AppCommands (source on GitHub) and add this to your main window:

<l:Window.AppCommandBindings>
    <AppCommandBinding AppCommand="BrowserBackward" Command="{Binding BackCommand}" />
</l:Window.AppCommandBindings>

The BackCommand will be invoked whenever the "back" button (whichever it is) is pressed.

Explanation

The XButton1 and XButton2 may default to being mapped to Back and Forward navigation, but this may not always be the case. Those buttons can be remapped to other functions, and some mice have more than five buttons. So, how do you actually listen for the Backwards navigation message?

  1. Hook up a WndProc.
  2. Listen for the WM_APPCOMMAND message.
  3. In the lparam, do some bit-twiddling and find the actual APPCOMMAND_* value.
  4. If it is the desired value (eg. APPCOMMAND_BROWSER_BACKWARD), do your thing.

Example:

var hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(mainWindow).Handle);
hwndSource.AddHook(WndProc);

// ...

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
    if (msg == WM_APPCOMMAND)
    {
        var appCommand = BitTwiddling(lparam);

        if (appCommand == APPCOMMAND_BROWSER_BACKWARD)
        {
            // do your thing
        }
    }
    return IntPtr.Zero;
}

The mainWindow is a reference to your main window (of type Window).

The BitTwiddling is basically: convert the IntPtr to an int, shift right 16 bits then mask with 0x7FFF.

Related