Search code examples
wxwidgetswebview2

How to capture keyboard events in wxWebView?


I want to handle keyboard events for wxWebView but it seems that the rendering engine, in my case WebView2 (edge backend) eats all the events and does not allow them to propagate to the parents.

I have bound a handler for key down events but it does not work.

ctrl_->Bind( wxEVT_KEY_DOWN, &WxHtml::onKeyDown, this );
void onKeyDown( wxKeyEvent& evt )
{
  fs::out << evt.GetKeyCode() << std::endl;
}

How should we get the events?

wx version: 3.2.1 OS: win 10 wxWebView backend: WebView2


Solution

  • As Igor said, some native windows, such as wxWebview, will consume all low level events such as key presses and mouse clicks so that they can't be handled with wxWidgets events.

    In the case of wxWebview, you can use JavaScript to handle key presses on the window and then try to navigate to an anchor with the key code. If you then handle the navigating event for the wxWebview window, you can retrieve the key code from the url for that event. Here is an example:

    #include <wx/wx.h>
    
    #include <wx/webview.h>
    #include <wx/uri.h>
    
    #ifdef __WXMSW__
        #include <wx/msw/webview_ie.h>
    #endif
    
    class MyFrame: public wxFrame
    {
        public:
            MyFrame();
        private:
            void OnWebViewNavigating(wxWebViewEvent& event);
            void OnKeyUp(wxKeyEvent&);
            wxWebView* m_webView;
    
    
    };
    
    MyFrame::MyFrame()
            :wxFrame(NULL, wxID_ANY, "Test Frame", wxDefaultPosition,
                     wxSize(600, 400))
    {
    #ifdef __WXMSW__
        wxWebViewIE::MSWSetEmulationLevel(wxWEBVIEWIE_EMU_IE11);
    #endif
        m_webView = wxWebView::New(this, wxID_ANY);
    
        wxString s =R"(<!DOCTYPE html>
                          <html lang="en">
                          <body>
                          <p>Hello</p>
                          <script>
                          document.onkeyup = function(e) {
                              let key_press = e.key;
                              let key_code = key_press.charCodeAt(0);
    
                              location.hash = "#" + key_code;
                          }
                          </script>
                          </body>
                          </html>
                          )";
    
        m_webView->SetPage(s,"");
    
        Bind(wxEVT_WEBVIEW_NAVIGATING,&MyFrame::OnWebViewNavigating, this);
        m_webView->Bind(wxEVT_KEY_UP, &MyFrame::OnKeyUp, this);
    }
    
    void MyFrame::OnKeyUp(wxKeyEvent& event)
    {
        // Escape has keycode 69, so to check if it was pressed:
        if ( event.GetKeyCode() == 69 )
        {
            // Change this to how you want to handle the escape key press.
            SetTitle("Escape was pressed");
        }
        else
        {
            // Handle any other key presses here.
        }
    }
    
    void MyFrame::OnWebViewNavigating(wxWebViewEvent& event)
    {
        wxKeyEvent keyEvent(wxEVT_KEY_UP);
        keyEvent.SetEventObject(event.GetEventObject());
        wxURI uri(event.GetURL());
        uri.GetFragment().ToLong(&keyEvent.m_keyCode);
    
        m_webView->ProcessWindowEvent(keyEvent);
    
        // Veto the action since we don't actually want to navigate.
        event.Veto();
    }
    
    class MyApp : public wxApp
    {
        public:
            virtual bool OnInit()
            {
                MyFrame* frame = new MyFrame();
                frame->Show();
                return true;
            }
    };
    
    wxIMPLEMENT_APP(MyApp);
    

    It's a bit of a pain, but it's the best way we can connect the Java Script and wxWidgets worlds unless you want to run a webserver locally.