Assigning keyboard shortcuts to controls.

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Assigning keyboard shortcuts to controls.

Post by apoorv569 »

I want to bind some controls like buttons and all to keyboard shortcuts for my application, I found the "Keyboard" sample provided with wxWidgets source, which led me to - https://docs.wxwidgets.org/3.0/classwx_ ... 9c34665121

I setup the accelerator entries and table as,

Code: Select all

    wxAcceleratorEntry entries[4];
    entries[0].Set(wxACCEL_NORMAL, WXK_SPACE, BCID_Play);
    entries[1].Set(wxACCEL_NORMAL, (int) 'L', BCID_Loop);
    entries[2].Set(wxACCEL_NORMAL, (int) 'S', BCID_Stop);
    entries[3].Set(wxACCEL_NORMAL, (int) 'P', BCID_Settings);

    wxAcceleratorTable accel(4, entries);
    this->SetAcceleratorTable(accel);
here are the controls definitions,

Code: Select all

    PlayButton = new wxButton(TopPanel, BCID_Play, "Play", wxDefaultPosition, wxDefaultSize, 0);
    LoopButton = new wxToggleButton(TopPanel, BCID_Loop, "Loop", wxDefaultPosition, wxDefaultSize, 0);
    StopButton = new wxButton(TopPanel, BCID_Stop, "Stop", wxDefaultPosition, wxDefaultSize, 0);
    SettingsButton = new wxButton(TopPanel, BCID_Settings, "Settings", wxDefaultPosition, wxDefaultSize, 0);
But except the "P" key which opens my settings dialog for the application, no other shortcut is working, space bar should play the audio file, L should toggle looping on/off, S should stop the playback.

The controls them selves work fine, i.e with mouse click.
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7477
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Assigning keyboard shortcuts to controls.

Post by ONEEYEMAN »

Hi,
How do you handle accelerators?
Where did you set them up - in the frame/dialog class or somewhere else?

Show the code of the frame itself and how do you handle them.

Thank you.
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

ONEEYEMAN wrote: Thu Mar 04, 2021 2:05 pm Hi,
How do you handle accelerators?
Where did you set them up - in the frame/dialog class or somewhere else?

Show the code of the frame itself and how do you handle them.

Thank you.
All the code above is in a class that inherits from wxPanel, which is the main panel of wxFrame.

After doing some debugging and adding some debug print statements, it looks like the play button only gets clicked with keybind space bar, if I have focus i.e I click on the panel where the play button is first, if I click on wxDVC it won't trigger play button, stop button and settings dialog keybind works fine i.e "S" and "P" where ever the focus, the loop button does not work at all no matter where the focus is, the loop button is wxToggleButton, do toggle button needs some extra thing to be enabled?
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

The MainFrame class only has this code in it,

Code: Select all

#include "MainFrame.hpp"

MainFrame::MainFrame(): wxFrame(NULL, wxID_ANY, "Sample Browser", wxDefaultPosition)
{
    int sizeH = 600, sizeW = 800;

    const std::string filepath = "config.yaml";

    Serializer serializer(filepath);

    sizeH = serializer.DeserializeWinSize("sizeH", sizeH);
    sizeW = serializer.DeserializeWinSize("sizeW", sizeW);

    this->SetSize(sizeW, sizeH);
    this->Center(wxBOTH);

    browser = new Browser(this);
}

MainFrame::~MainFrame(){}
The accelerators are setup in another class "Browser" which inherits from wxPanel, which is the main panel of wxFrame, the whole UI is defined in this class.

This is the constructor of "Browser".

Code: Select all

Browser::Browser(wxWindow* window)
    : wxPanel(window, wxID_ANY, wxDefaultPosition, wxDefaultSize),
      serialize(configFilepath)
{
    // Generate a yaml file if it does not exist,
    // for configuring SampleBrowser.
    configFilepath = "config.yaml";
    databaseFilepath = "Samples.db";

    // Initializing to start as false
    autoplay = false;
    muted = false;
    loop = false;
    stopped = false;
.
.
.
}
and somewhere down in this constructor is this code, after all the controls are Binded,

Code: Select all

    wxAcceleratorEntry entries[5];
    entries[0].Set(wxACCEL_NORMAL, WXK_SPACE, BCID_Play);
    entries[1].Set(wxACCEL_NORMAL, (int) 'L', BCID_Loop);
    entries[2].Set(wxACCEL_NORMAL, (int) 'S', BCID_Stop);
    entries[3].Set(wxACCEL_NORMAL, (int) 'M', BCID_Mute);
    entries[4].Set(wxACCEL_NORMAL, (int) 'P', BCID_Settings);

    wxAcceleratorTable accel(5, entries);
    this->SetAcceleratorTable(accel);
Except the P and S key no other keybind works, Spacebar only works if I click on the panel the button is drawn on first. S and P work from where the focus is. I can provide any more code if needed.
User avatar
doublemax
Moderator
Moderator
Posts: 19159
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Assigning keyboard shortcuts to controls.

Post by doublemax »

Just a wild guess: Try assigning the wxAcceleratorTable to the main frame.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

doublemax wrote: Tue Mar 09, 2021 8:03 pm Just a wild guess: Try assigning the wxAcceleratorTable to the main frame.
I moved the wxAcceleratorTable to the main frame, had to add the header that has the control ID enums.

Code: Select all

#include "ControlID_Enums.hpp"
#include "MainFrame.hpp"

MainFrame::MainFrame(): wxFrame(NULL, wxID_ANY, "Sample Browser", wxDefaultPosition)
{
    int sizeH = 600, sizeW = 800;

    const std::string filepath = "config.yaml";

    Serializer serializer(filepath);

    sizeH = serializer.DeserializeWinSize("sizeH", sizeH);
    sizeW = serializer.DeserializeWinSize("sizeW", sizeW);

    this->SetSize(sizeW, sizeH);
    this->Center(wxBOTH);

    wxAcceleratorEntry entries[5];
    entries[0].Set(wxACCEL_NORMAL, WXK_SPACE, BCID_Play);
    entries[1].Set(wxACCEL_NORMAL, (int) 'L', BCID_Loop);
    entries[2].Set(wxACCEL_NORMAL, (int) 'S', BCID_Stop);
    entries[3].Set(wxACCEL_NORMAL, (int) 'M', BCID_Mute);
    entries[4].Set(wxACCEL_NORMAL, (int) 'P', BCID_Settings);

    wxAcceleratorTable accel(5, entries);
    this->SetAcceleratorTable(accel);

    browser = new Browser(this);
}

MainFrame::~MainFrame(){}
but now, neither keybinding work, not even the P and S that was working before.
User avatar
doublemax
Moderator
Moderator
Posts: 19159
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Assigning keyboard shortcuts to controls.

Post by doublemax »

but now, neither keybinding work, not even the P and S that was working before.
Did you also move the event handler(s) to the frame? This is just for a test.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

doublemax wrote: Tue Mar 09, 2021 9:44 pm
but now, neither keybinding work, not even the P and S that was working before.
Did you also move the event handler(s) to the frame? This is just for a test.
Just moving the function like this does nothing,

Code: Select all

#include "ControlID_Enums.hpp"
#include "MainFrame.hpp"

MainFrame::MainFrame(): wxFrame(NULL, wxID_ANY, "Sample Browser", wxDefaultPosition)
{
    int sizeH = 600, sizeW = 800;

    const std::string filepath = "config.yaml";

    Serializer serializer(filepath);

    sizeH = serializer.DeserializeWinSize("sizeH", sizeH);
    sizeW = serializer.DeserializeWinSize("sizeW", sizeW);

    this->SetSize(sizeW, sizeH);
    this->Center(wxBOTH);

    wxAcceleratorEntry entries[5];
    entries[0].Set(wxACCEL_NORMAL, WXK_SPACE, BCID_Play);
    entries[1].Set(wxACCEL_NORMAL, (int) 'L', BCID_Loop);
    entries[2].Set(wxACCEL_NORMAL, (int) 'S', BCID_Stop);
    entries[3].Set(wxACCEL_NORMAL, (int) 'M', BCID_Mute);
    entries[4].Set(wxACCEL_NORMAL, (int) 'P', BCID_Settings);

    wxAcceleratorTable accel(5, entries);
    this->SetAcceleratorTable(accel);

    browser = new Browser(this);
}

void Browser::OnClickMute(wxCommandEvent& event)
{
    if (MuteButton->GetValue())
    {
        MediaCtrl->SetVolume(0.0);
        muted = true;
    }
    else
    {
        MediaCtrl->SetVolume(1.0);
        muted = false;
    }
}
But do you mean, like drawing the control from MainFrame, and Bind() and all to MainFrame also? That would mean moving a lot of code..
User avatar
doublemax
Moderator
Moderator
Posts: 19159
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Assigning keyboard shortcuts to controls.

Post by doublemax »

But do you mean, like drawing the control from MainFrame, and Bind() and all to MainFrame also? That would mean moving a lot of code..
Like i said, just for a test to find out if this solves the issue.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

doublemax wrote: Wed Mar 10, 2021 6:12 am
But do you mean, like drawing the control from MainFrame, and Bind() and all to MainFrame also? That would mean moving a lot of code..
Like i said, just for a test to find out if this solves the issue.
Okay I tried making a separate demo project to test this out, I tried with both trying to use wxAcceleratorTabel in MainFrame and Browser it works the same way, only S and P works anywhere, Spacebar only works when I click on the panel the button is on, and M and L don't work at all they are toggle buttons. However I noticed that if I empty the wxDVLC then the spacebar also works, seems like when the focus is on wxDVLC and it has data in it, the spacebar tries to do something there, instead of keybind action.

Here is the sample project I made to test this - https://gitlab.com/apoorv569/cpp-projec ... eratorTest
User avatar
doublemax
Moderator
Moderator
Posts: 19159
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Assigning keyboard shortcuts to controls.

Post by doublemax »

After playing around with your sample a bit, i think the problem is that accelerators only work for buttons and menus. Not for toggle buttons, checkboxes or anything else.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

doublemax wrote: Thu Mar 11, 2021 10:38 am After playing around with your sample a bit, i think the problem is that accelerators only work for buttons and menus. Not for toggle buttons, checkboxes or anything else.
I see, is there something else I can do to be able to bind toggle buttons as well? Also what about the play button only responding when focus is not on wxDVLC?
User avatar
doublemax
Moderator
Moderator
Posts: 19159
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Assigning keyboard shortcuts to controls.

Post by doublemax »

apoorv569 wrote: Thu Mar 11, 2021 1:03 pm Also what about the play button only responding when focus is not on wxDVLC?
That worked for me. However, i'm not sure it's a good idea to use hotkeys without any modifier, they will always conflict with something. E.g. right now you can't enter any of the accelerator letters into the search text control.

If you really, really want this, i would override wxAppConsole::FilterEvent() and write my own dispatcher for the key events.
https://docs.wxwidgets.org/trunk/classw ... c6f602e20c
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: Assigning keyboard shortcuts to controls.

Post by apoorv569 »

doublemax wrote: That worked for me. However, i'm not sure it's a good idea to use hotkeys without any modifier, they will always conflict with something. E.g. right now you can't enter any of the accelerator letters into the search text control.
If the wxDVLC is empty the space key works, if it has items, and one of them is selected/have focus, then it won't work.

I tried binding using modifiers,

Code: Select all

    wxAcceleratorEntry entries[5];
    entries[0].Set(wxACCEL_CTRL, (int) 'P', BC_Play);
    entries[1].Set(wxACCEL_CTRL, (int) 'L', BC_Loop);
    entries[2].Set(wxACCEL_CTRL, (int) 'S', BC_Stop);
    entries[3].Set(wxACCEL_CTRL, (int) 'M', BC_Mute);
    entries[4].Set(wxACCEL_CTRL, (int) 'O', BC_Settings);

    wxAcceleratorTable accel(5, entries);
    this->SetAcceleratorTable(accel);
but it is still the same, if I press "CTRL + P" the system beeps, i.e the small speaker that is inside cabinet. "CTRL + S" and "CTRL+O" works, and the toggle you say won't work so..
doublemax wrote: If you really, really want this, i would override wxAppConsole::FilterEvent() and write my own dispatcher for the key events.
https://docs.wxwidgets.org/trunk/classw ... c6f602e20c
I see, is there example code I can look at? Is it like, I can tell it that if focus is on wxDVLC process these events, or something like it?
User avatar
doublemax
Moderator
Moderator
Posts: 19159
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Assigning keyboard shortcuts to controls.

Post by doublemax »

Code: Select all

int MyApp::FilterEvent(wxEvent &event)
{
  if( event.GetEventType() == wxEVT_KEY_DOWN /* || event.GetEventType() == wxEVT_KEY_UP */ )
  {
    wxWindow *focusWindow = wxWindow::FindFocus();
    if( focusWindow != NULL ) {
      const wxString &className = focusWindow->GetClassInfo()->GetClassName();
      // if textctrl has focus, let all keys through
      if( className == wxT("wxTextCtrl") )
        return wxEventFilter::Event_Skip;
    }

    wxKeyEvent *ke = wxDynamicCast( &event, wxKeyEvent );
    if( ke != NULL )
    {
      switch( ke->GetKeyCode() )
      {
        case ' ':
          //wxLogMessage("space pressed - do something here");
          wxLogDebug("space pressed - do something here");
          return wxEventFilter::Event_Processed;
        break;
      }
    }
  }
  return -1;
}
This just demonstrates the general idea, in practice, you'll probably have to deal with quite a few edge cases.

For communication between the app and the submodules, i'd suggest some kind of signal-slot mechanism, so that you don't have to introduce too many global dependencies, e.g. http://sigslot.sourceforge.net/
Use the source, Luke!
Post Reply