Catching key events globally

From WxWiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Keyboard events go to the component that currently has focus and do not propagate to the parent; if you are trying to catch key events globally it can thus be a little tricky. Here are a few ways to solve this problem - keep in mind there are probably more than presented here.


Before getting started, some helpful notes about cases where you may catching the wrong event or where you may not need global key catching at all :

  • Many components will only receive key events if they have the wxWANTS_CHARS style flag enabled; then, you need to catch EVT_CHAR rather than or in addition to EVT_KEY_DOWN.
  • For catching Enter presses on text controls, use style flag wxTE_PROCESS_ENTER, and catch event EVT_TEXT_ENTER.

wxEVT_CHAR_HOOK

Catching wxEVT_CHAR_HOOK may be useful in certain situations. See wxEVT_CHAR_HOOK in https://docs.wxwidgets.org/trunk/classwx_key_event.html Note the use of flag wxWANTS_CHARS


Sample for catching all events within a frame :

#include "wx/wx.h" 

class MyApp: public wxApp
{
    virtual bool OnInit();
};

class MyFrame: public wxFrame
{
public:

    MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);

    void OnKeyDown(wxKeyEvent& event);
};

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
    MyFrame *frame = new MyFrame( "Hello World", wxPoint(50,50), wxSize(450,340) );
    frame->Show(TRUE);
    SetTopWindow(frame);
    return TRUE;
} 

MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
: wxFrame((wxFrame *)NULL, -1, title, pos, size)
{
    wxPanel* mainPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
    mainPane->Bind(wxEVT_CHAR_HOOK, &MyFrame::OnKeyDown, this);
}

void MyFrame::OnKeyDown(wxKeyEvent& event)
{
    wxMessageBox(wxString::Format("KeyDown: %i\n", (int)event.GetKeyCode()));
    event.Skip();
}

Filter events

Source: http://forums.wxwidgets.org/viewtopic.php?p=66017#66017

Override wxApp::FilterEvent. This function is called early in event-processing, so you can do things like this (for F1):

int MyApp::FilterEvent(wxEvent& event)
{
    if ((event.GetEventType() == wxEVT_KEY_DOWN) && 
        (((wxKeyEvent&)event).GetKeyCode() == WXK_F1))
    {
        frame->OnHelpF1( (wxKeyEvent&)event );
        return true;
    }

    return -1;
}

Returning -1 for events in which you're not interested tells the event system to continue to process them as normal. You could instead do: return wxApp::FilterEvent(event); which would have a similar effect.

This will work most of the time. It will fail if

  • Your application is not in the foreground. If you wish to catch keys even when your app is in the background, you will need to use platform-specific code
  • Your window-manager grabs the key for itself, in which case your app won't see it.
  • (Using wxGtk---I don't know about other platforms) the key is pressed while a modal wxDialog is showing, and the dialog doesn't have keyboard focus. The solution is to call SetFocus() on one of the dialog's children before calling ShowModal().

Recursive connect

You can recursively ->Connect() all components in a frame, therefore you will catch events no matter where the focus is.

void
MyDialog::createChilds(void)
{
  // create the childs: panels, textctrls, buttons, ...
 
  // ...
 
  /////////////////////////////////////
  // after creation:
  this->connectKeyDownEvent(this);
 
}


void
MyDialog::connectKeyDownEvent(wxWindow* pclComponent)
{
  if(pclComponent)
  {
    pclComponent->Connect(wxID_ANY,
                          wxEVT_KEY_DOWN,
                          wxKeyEventHandler(MyDialog::onKeyDown),
                          (wxObject*) NULL,
                          this);

    wxWindowListNode* pclNode = pclComponent->GetChildren().GetFirst();
    while(pclNode)
    {
      wxWindow* pclChild = pclNode->GetData();
      this->connectKeyDownEvent(pclChild);
     
      pclNode = pclNode->GetNext();
    }
  }
}

void
MyDialog::onKeyDown(wxKeyEvent& event)
{
  // do your event-processing here
 
}

Code author : clyde729 (Source : http://wxforum.shadonet.com/viewtopic.php?t=9361)


Make a custom event handler

This code is inspired from code by Rostfrei. With it, you can simply create a class like the CEventPropagator below, then call the 'registerFor' function when your frame/panel is created (note that in the code below the 'registerFor' function is not recursive, so will only affect direct children, though it could be modified to be)

// Inspired from code by 'rostfrei'

#include "wx/wx.h"
#include <iostream>

// ------------------------------------------------------------------------------------
// The CEventPropagator cutom event handler : it forces key events to propagate up

class CEventPropagator : public wxEvtHandler
{
public:
    CEventPropagator();
    static void registerFor(wxWindow* win);

private:
    void onKeyDown(wxKeyEvent& aEvent);
    void onKeyUp(wxKeyEvent& aEvent);
};


// ------------------------------------------------------------------------------------

CEventPropagator::CEventPropagator()
{
    // Event connections
    this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(CEventPropagator::onKeyDown));
    this->Connect(wxEVT_KEY_UP,   wxKeyEventHandler(CEventPropagator::onKeyUp));
}

void CEventPropagator::onKeyDown(wxKeyEvent& aEvent)
{
    //printf("CEventPropagator::onKeyDown\n");
    aEvent.ResumePropagation(1);
    aEvent.Skip();
}

void CEventPropagator::onKeyUp(wxKeyEvent& aEvent)
{
    //printf("CEventPropagator::onKeyUp\n");
    aEvent.ResumePropagation(1);
    aEvent.Skip();
}

void CEventPropagator::registerFor(wxWindow* win)
{
    wxWindowListNode* childNode = win->GetChildren().GetFirst();
    while (childNode)
    {
        childNode->GetData()->PushEventHandler(new CEventPropagator());
        childNode = childNode->GetNext();
    } 
}

// ------------------------------------------------------------------------------------
// Example of use

class MyFrame : public wxFrame
{
public:
    MyFrame() : wxFrame(NULL, wxID_ANY,  wxT("Hello wxWidgets"), wxPoint(50,50), wxSize(800,600))
    {
        wxPanel* panel = new wxPanel(this);
        wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
        
        for (int n=0; n<15; n++)
        {
            sizer->Add(new wxTextCtrl(panel, wxID_ANY, wxT("Some Widget")), 0, wxALL, 5);
        }
        panel->SetSizer(sizer);
                          
        CEventPropagator::registerFor(panel);
        
        panel->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MyFrame::onKeyDown), NULL, this);

    }
    
    void onKeyDown(wxKeyEvent& evt)
    {
        std::cout << "Pressed key {" << evt.GetKeyCode() << "}\n";
        evt.Skip();
    }
    
};

class MyApp: public wxApp
{
    wxFrame* m_frame;
public:
    
    bool OnInit()
    {
        m_frame = new MyFrame();
        m_frame->Show();
        return true;
    } 
    
};

IMPLEMENT_APP(MyApp)

System-wide Hot Key

bool wxWindow::RegisterHotKey(int hotkeyId, int modifiers, int virtualKeyCode)

Note: This is currently (wxWidgets 2.8.11) Windows only

Menu Hot key

You can also make a hot key part of a menu item

editMenu->Append(MENU_EDIT_COPY,  _("Copy\tCtrl-C"));

Accelerator table

MyFrame::MyFrame(...) : wxFrame
{
    wxAcceleratorEntry entries[14];
    entries[0].Set(wxACCEL_CTRL, (int) 'O', MENU_LOAD_FILE);
    ...
    entries[12].Set(wxACCEL_NORMAL, WXK_SPACE, KEY_SPACE);
    entries[13].Set(wxACCEL_NORMAL, WXK_ESCAPE, KEY_ESC);
    wxAcceleratorTable accel(14, entries);
    SetAcceleratorTable(accel);
}

...

EVT_MENU(KEY_ESC,MyFrame::CheckAbort)
EVT_MENU(KEY_SPACE,MyFrame::CheckAbort)

Source : http://wxforum.shadonet.com/viewtopic.php?t=18140 Author : celstark

Regular Polling

This is helpful to get a game-like behaviour : you can simply poll for key states using wxGetKeyState() (for instance in a wxTimer, or in idle events, etc.)

Example

Add timer in your derived wxFrame or wxApp

// In event table
EVT_TIMER(wxID_ANY, SuperMouserApp::OnTimer)

// On initialize
timer_ = new wxTimer(this, wxID_ANY);
timer_->Start(30);

Then the callback (I prefer to add Stop() while in OnTimer):

void SuperMouserApp::OnTimer(wxTimerEvent& event)
{
	timer_->Stop();
	
	if (wxGetKeyState(WXK_CONTROL) && wxGetKeyState(WXK_SHIFT)) {
		// ...
	}
	if (wxGetKeyState(wxKeyCode('H'))) {
		// ...
	}

	timer_->Start();
}