If you believe less is more… If you are a conscientious and diligent developer… If you have ever thought “there’s got to be a better way…” This is the blog for you! This is all about finding a straightforward solution to efficient keyboard navigation code in QML using Pane’s FocusPolicy.
If you landed here because you really want the QML tab-focus code, then feel free to skip straight to “Ah ha!” To hear about the thought process that led to this QML solution, stick right here with me.
If you’re a software engineer with any appreciable length of tenure, you’ll surely relate: Some of my most memorable “wins” in software emerged from finding a way to not write any code—or write very little—and still solve the problem at hand.
Most of these wins were preceded by looking at some existing code and thinking: “There has got to be a better way.” or “Someone has definitely solved this problem before.”
Today’s blog post is a mini case study in this philosophy of doing more with less. It’s also about reading the scattered tea leaves of framework documentation. Synthesizing disparate parts of a body of documentation is a subtle art. In the case at hand, it was well worth it.
A big reason that working at 219 Design continues to be ideal for me is that my team shares these values. We find efficiency by being conscientious and diligent, not through “cowboy coding” and “clever (fragile)” solutions. Being on a team studded with experienced practitioners from a range of backgrounds also means that when I rant aloud that “there has got to be a better way” it is highly likely one of my teammates will answer “there is, and I can show you.”
The solidarity extends beyond our office walls. Our clients also appreciate our efficiency, our frugality with code, and our willingness to thoroughly research the solution space.
Without further ado: a tale of efficient keyboard navigation code in QML.
Having produced a string of touch-screen GUI(s) in QML, our software engineers had managed to go several years without worrying about keyboard navigability. Recently, however, we took a renewed interest.
The toy example used here is based on a real project. In our project, the client requirements didn’t specify tab-navigation per se. Instead, the requirement was navigability using a custom peripheral device. (Picture something like a joystick, for example.) We determined the most straightforward solution was to map the peripheral’s signals to keypresses. This allows the QML to remain ignorant of exotic device details and allows us to leverage the readily-available keyboard features in QML. It also allows us to test the QML code even when the custom peripheral is not available.
If you go searching for “keyboard focus” or “keyboard navigation” in the Qt QML docs, you will likely come across:
Well… I am here to explain that there is a separate option that uses none of the above! If you want to skip straight to the very cool separate option, skip ahead to “Ah ha!” Otherwise, we’ll start with the stuff from the Qt docs.
Let’s begin with a FocusScope example based heavily on the first bits of QML code you will see on a Qt docs page titled Keyboard Focus in Qt Quick:
You can download a full working example from GitHub.
“Widget” is a colored rectangle that will also show the text “I have focus” when the keyboard focus is upon it. The above code sets “focus: true” on the yellow Widget. As expected, if you launch the code, you see:
Our next task is to allow the focus to move from one Widget to the next, ideally by pressing the TAB key. Here is where we briefly turn down a corner that we will later deem a dead end.
The following is one common way to handle keyboard navigation in QML (but stay tuned for an alternative):
Now you can run the code in qmlscene, press TAB and SHIFT+TAB on your keyboard, and you will see behavior like this:
The behavior is what we want, but…
Did you notice how many instances of “KeyNavigation” snippets we added in this second version?
If I want the user to be able to keyboard navigate through all controls in my application, must I add hand-crafted code to each control to explicitly configure its predecessor and successor? That seems crazy!
If you have used a keyboard combined with a desktop application (such as, e.g., a web browser) any time in the last 20 years, you probably know that the following two keystrokes are conventional:
Here is a Mozilla document from 2009 that discusses the concept.
Most desktop application frameworks—including “classic Qt widgets”—give you tab key navigation for free!! Why can’t I have that in QML?
It turns out that you can.
So if you wrap your Rectangle with a Pane, set the Pane’s FocusPolicy appropriately, then you can remove the need for the FocusScope, and remove the need for the fragile KeyNavigation lines, and you can get tab-key navigation for free, just like it’s 2009!!
Using these observations, we end up with simplified “main.qml” as follows:
Where did the navigation pieces go? They are consolidated in one place, encapsulated in the implementation of “Widget”. The task is achieved almost entirely by configuring the property “focusPolicy: Qt.StrongFocus” inside of Widget, as shown here.
Be sure to check out the full code sample and experiment with it yourself.
The next time you find yourself thinking “there has got to be a better way”, please call us! We hear you.