QML Mouse/Keyboard Event Handling
By: Brad
In QML you can accept user inputs via the mouse with the MouseArea element and via the keyboard with the Keys element. These elements have built in signals and slots to handle input events (i.e. click event, key press event); however, they work a little differently then the other signals/slots in QML. These events bubble up through the application hierarchy being processed by each applicable event handle until accepted.
Rectangle { width: 200 height: 200 MouseArea { anchors.fill: parent onClicked: { console.log("Bar"); } } MouseArea { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: 100 onClicked: { console.log("Foo"); } } }
My meaning is that in the above example if the user clicked the mouse in the bottom 100px of the Rectangle both onClicked event handlers will execute; that is “Foo” and “Bar” will be printed.
Assuming that what we wanted is for just to have “Foo” printed we have to tell the event handling system that the event was handled in the first event handler and not to bubble it up the application hierarchy. We do this with the accepted property of the MouseEvent object.
The event handlers of the MouseArea element take in the MouseEvent object as a parameter named mouse; therefore to tell the event handling system that the event was processed we need to add mouse.accepted = true to the above code example.
Rectangle { width: 200 height: 200 MouseArea { anchors.fill: parent onClicked: { console.log("Bar"); } } MouseArea { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: 100 onClicked: { console.log("Foo"); mouse.accepted = true } } }
With the above change now only “Foo” will be printed.
For mouse clicks setting the mouse.accepted to true to me seems to be the 99%; off the top of my head I’m not sure of to many instances where I’d want overlapping MouseAreas to process the same mouse click. Its a bit of a shame that this isn’t the default behaviour. However the same bubbling up of event processing is also true for keyboard inputs and there it does make sense to allow the event to bubble up on default.
The Keys element’s event handlers work much the same way as the MouseArea’s event handlers where events are bubbled up the application hierarchy until reaching the root or explicitly accepted. Similarly they take an event object but instead of MouseEvent its the KeyEvent object and the parameter is named differently too (in the MouseArea handlers its called mouse but in the Keys event handlers its called event).
Rectangle { width: 200 height: 200 Column { MyControl { Keys.onPressed: { console.log("Foo"); } } MyOtherControl { } } Keys.onPressed: { console.log("Bar"); } }
In the above code example if the MyOtherControl or the root element has focus and I press any key on the keyboard the string “Bar” will be printed; if however the MyControl has focus and I press any key on the keyboard both strings “Foo” and “Bar” will be printed.
Now what I want to have happen might be to have the string “Foo” printed if the Enter key is pressed while focus is on the MyControl element but I might also want a global F1 key that will print “Bar” regardless of what controls in the UI have focus. This is where the bubbling up of system events comes in hand; because I can re-write the above to allow the MyControl’s onPressed event handler to process the key press while still allowing the root to accept key presses at any time.
Rectangle { width: 200 height: 200 Column { MyControl { Keys.onPressed: { if ( event.key === Qt.Key_Return ) { console.log("Foo"); event.accepted = true } } } MyOtherControl { } } Keys.onPressed: { console.log("Bar"); } }
Now if the focus is on the root or the MyOtherControl element I can press any key on the keyboard, including the enter key, and I’ll get “Bar” printed; however, if the focus is on the MyControl element I’ll still get “Bar” printed when I press any key other then the enter key but if I press the enter key only “Foo” will be printed.
Now to make the root elements onPressed event handler only print “Bar” on the F1 key press I can add a similar condition to its event handler.
Rectangle { width: 200 height: 200 Column { MyControl { ... } MyOtherControl { ... } } Keys.onPressed: { if ( event.key === Qt.Key_F1 ) { console.log("Bar"); event.accepted = true } } }
I have found issues however when the implicit non-acceptance of the event hasn’t always worked so sometimes its not a bad idea to be explicit about things by setting the event.accepted to false.
MyControl { Keys.onPressed: { if ( event.key === Qt.Key_Return ) { console.log("Foo"); event.accepted = true } else { event.accepted = false } } }
Well there you have it the QML Event Handling System in a nut shell. I encourage you to look up the MouseEvent and KeyEvent objects and the MouseArea and Keys elements in the Qt documentation for more details.
If you have any questions or comments feel free to leave a comment below and I’ll respond as time permits
Until next time think imaginatively and design creatively