PDA

View Full Version : Space key doesn't trigger keyPressEvent



Cruz
20th September 2014, 13:07
Hello there!

The main object of my application is a QMainWindow where I grabKeyboard() in the constructor to handle all keyboard input in the keyPressEvent of the main window. Additionally, there are a bunch of QActions in a QMenuBar that listen and react to key presses directly. All this works wonderfully, except for the space key. When I press any key that is not bound to an action, the keyPressEvent should be called, but in the case of the space key it does not get called. I did not bind the space key to any action. Does anyone know why and how to fix it?

Thanks
Cruz

wysota
20th September 2014, 16:03
Hello there!

The main object of my application is a QMainWindow where I grabKeyboard() in the constructor to handle all keyboard input
Never do that.


in the keyPressEvent of the main window. Additionally, there are a bunch of QActions in a QMenuBar that listen and react to key presses directly. All this works wonderfully, except for the space key. When I press any key that is not bound to an action, the keyPressEvent should be called, but in the case of the space key it does not get called. I did not bind the space key to any action. Does anyone know why and how to fix it?

Implement proper key handling using focus and keyPressEvent() handlers in proper places.

Cruz
20th September 2014, 16:27
No, that neither explains where the problem is, nor does it offer a possible solution. It would only complicate my application by heaps.

wysota
20th September 2014, 16:40
No, that neither explains where the problem is, nor does it offer a possible solution. It would only complicate my application by heaps.

grabKeyboard() causes all key events to reach your widget, regardless whether they are destined for it or not. You should not require it unless you have misdesigned your key event handling. So fixing your design is the first step to make your application working. Until you do that it is not possible to even start looking for the problem in any organized way using mechanisms Qt provides to debug your events.

Cruz
20th September 2014, 17:17
All key presses are destined for the main window. That's exactly what I want to have and that's what grabKeyboard() does. It's a legitimate, Qt supported way to achieve this. I'm sure there are easier ways to debug this problem than to refactor the entire application to a more complicated form.

I can for example remove everything from the QMainWindow and see if it reacts to the space key with the "proper" way to handle keyboard input (i.e. no grabKeyboard()). Yes it does. Interestingly, even with grabKeyboard() the space key works fine.

And now that I readded all components piece by piece, I did come across a QAction that was tied to the space key. I didn't find it at first, because "space" was spelled with a lower case. :/ My bad.

anda_skoa
20th September 2014, 17:45
Why don't you use a global event filter instead of the grab keyboard hack?

Cheers,
_

Cruz
20th September 2014, 18:04
That's a good question. By the time when I wrote this application, I was a Qt beginner and event filtering was not within my horizon. The grabKeyboard() hack solved all my problems with one line so I was content with it (and I still am). Now that you brought it up, I looked into event filtering again. Yes, I suppose I could install an event filter on the QMainWindow and intercept all keyboard events that I'm interested in. Right now, the keyboard handling is in one central place, namely the keyPressEvent() of the main window, which I think is a nice place for it. Moving that to a separate filter object is something I would not find more beautiful. I suppose I could use the QMainWindow as an event filter for itself, in which case I would merely have to change the keyPressEvent() to an event filter function and then register the window as an event filter for itself instead of grabKeyboard(). Hm, does that really make things any easier or more beautiful?

Oh and most importantly, with the event filter, would the key strokes for the QActions be intercepted? Because they combine nicely with the grabKeyboard() method.

anda_skoa
20th September 2014, 18:22
I would even consider installing the event filter on the QApplication object, all events pass through it.

And as long as you don't filter them out they'll still reach whatever object they were sent to.

Cheers,
_

wysota
20th September 2014, 21:56
Yes, I suppose I could install an event filter on the QMainWindow and intercept all keyboard events that I'm interested in.
That would be exactly equivalent of just reimplementing keyPressEvent for the main window. As long as child widgets do not handle key events themselves, they will be forwarded to the parent widget until they reach your main window where you will handle them. No need for any grabKeyboard() hacks.

And regarding event filtering, I was also going to suggest installing the event filter on the application object or even subclassing QApplication and reimplementing notify() however I think all these solutions are hacks (in this particular situation) compared to proper event handling which I'm sure can easily be done without having to "complicate your application by heaps". Worst case scenario, you'd have to iterate over widgets and make sure they don't accept focus and then have your main object central widget accept focus and reimplement its keyPressEvent handler. All that can be done with 3-4 lines of code or by a quick one-time script run.

Cruz
20th September 2014, 22:30
As long as child widgets do not handle key events themselves...

I don't know why you assume you have full information about my application, but to set this straight, the application contains widgets that do their own key event handling in undesired ways. For example, QSliders handle the arrow keys when they have focus. But the arrow keys have a different function in my program and it's important that the arrow keys do what they do no matter where the focus is. I would have to subclass QSlider or filter events or do whatever acrobatics is necessary to stop them from handling keys I need for something else. This goes for a number of widgets, all of which would require individual attention.

Then there are also lovely cases where more than one widget needs to do something in response to a single key press, no matter where the focus is. As far as I can tell this is also not something that "proper" key handling is prepared for, because the widget that has the focus and handles the event, consumes the event too. So I would have to "hack" something to circumvent the default event handling mechanism and to forward the event to multiple widgets. Just because there are predefined Qt ways to accomplish this, it doesn't make it any less complex and cumbersome.

The grabKeyboard() hack solves it all with one line and puts all keyboard handling in one place. It's easy to find, to understand, and to maintain.

wysota
20th September 2014, 23:09
The grabKeyboard() hack solves it all with one line and puts all keyboard handling in one place. It's easy to find, to understand, and to maintain.

I still think this is a hack. The sort of "offcial" way of providing your own event handling is to reimplement QCoreApplication::notify().

If grabKeyboard() works for you then that's fine. However if you are only after modifying some keys globally then using either of the two mentioned solutions (notify() or application event filter) is a more clean approach without getting you into a God Object design anti-pattern (http://en.wikipedia.org/wiki/God_object). Simply delegate the "custom" behaviours to a dedicated place and leave everything else to the default mechanism.

E.g.:


bool XXX::eventFilter(QObject *object, QEvent *event) {
if(event->type() != Qt::KeyPressEvent) return false;
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if(keyEvent->key() == Qt::Key_Left && object != myMainWindow) {
myMainWindow->keyPressEvent(keyEvent); // or do whatever else you'd like, e.g. event->ignore() letting it propagate until the main window
return true; // eat the left arrow key event
}
return false;
}