Keyboard Focus in QML

When a key is pressed or released, a key event is generated and delivered to the focused QML Item. To facilitate the construction of reusable components and to address some of the cases unique to fluid user interfaces, the QML items add aged scope based extension to Qt's traditional keyboard focus model.

Key Handling Overview

When the user presses or releases a key, the following occurs:

  1. Qt receives the key action and generates a key event.
  2. If the Qt widget containing the QDeclarativeView has focus, the key event is delivered to it. Otherwise, regular Qt key handling continues.
  3. The key event is delivered by the scene to the QML Item with active focus. If no Item has active focus, the key event is ignored and regular Qt key handling continues.
  4. If the QML Item with active focus accepts the key event, propagation stops. Otherwise the event is "bubbled up", by recursively passing it to each Item's parent until either the event is accepted, or the root Item is reached.

    If the Rectangle element in the following example has active focus and the A key is pressed, it will bubble up to its parent. However, pressing the B key will bubble up to the root item and thus subsequently be ignored.

    1.                         Rectangle  {
    2.     width: 100; height: 100
    3.     focus: true
    4.     Keys.onPressed:  {
    5.         if (event.key == Qt.Key_A)  {
    6.             console.log('Key A was pressed');
    7.             event.accepted = true;
    8.         }
    9.     }
    10. }
  5. If the root Item is reached, the key event is ignored and regular Qt key handling continues.

See also the Keys attached property and KeyNavigation attached property.

Querying the Active Focus Item

Whether or not an Item has active focus can be queried through the property Item::activeFocus property. For example, here we have a Text element whose text is determined by whether or not it has active focus.

  1.     Text  {
  2.         text: activeFocus ? "I have active focus!" : "I do not have active focus"
  3.     }

Acquiring Focus and Focus Scopes

An Item requests focus by setting the focus property to true.

For very simple cases simply setting the focus property is sometimes sufficient. If we run the following example with the QML Viewer, we see that the keyHandler element has active focus and pressing the A, B, or C keys modifies the text appropriately.

  1.                 Rectangle  {
  2.     color: "lightsteelblue"; width: 240; height: 25
  3.     Text  { id: myText }
  4.     Item  {
  5.         id: keyHandler
  6.         focus: true
  7.         Keys.onPressed:  {
  8.             if (event.key == Qt.Key_A)
  9.                 myText.text = 'Key A was pressed'
  10.             else if (event.key == Qt.Key_B)
  11.                 myText.text = 'Key B was pressed'
  12.             else if (event.key == Qt.Key_C)
  13.                 myText.text = 'Key C was pressed'
  14.         }
  15.     }
  16. }

However, were the above example to be used as a reusable or imported component, this simple use of the focus property is no longer sufficient.

To demonstrate, we create two instances of our previously defined component and set the first one to have focus. The intention is that when the A, B, or C keys are pressed, the first of the two components receives the event and responds accordingly.

The code that imports and creates two MyWidget instances:

  1. //Window code that imports MyWidget
  2. Rectangle  {
  3.     id: window
  4.     color: "white"; width: 240; height: 150
  6.     Column  {
  7.         anchors.centerIn: parent; spacing: 15
  9.         MyWidget  {
  10.             focus: true             //set this MyWidget to receive the focus
  11.             color: "lightblue"
  12.         }
  13.         MyWidget  {
  14.             color: "palegreen"
  15.         }
  16.     }
  17. }

The MyWidget code:

  1.                 Rectangle  {
  2.     id: widget
  3.     color: "lightsteelblue"; width: 175; height: 25; radius: 10; smooth: true
  4.     Text  { id: label; anchors.centerIn: parent}
  5.     focus: true
  6.     Keys.onPressed:  {
  7.         if (event.key == Qt.Key_A)
  8.             label.text = 'Key A was pressed'
  9.         else if (event.key == Qt.Key_B)
  10.             label.text = 'Key B was pressed'
  11.         else if (event.key == Qt.Key_C)
  12.             label.text = 'Key C was pressed'
  13.     }
  14. }

We would like to have the first MyWidget object to have the focus by setting its focus property to true. However, by running the code, we can confirm that the second widget receives the focus.

Looking at both MyWidget and window code, the problem is evident - there are three elements that set the focus property set to true. The two MyWidget sets the focus to true and the window component also sets the focus. Ultimately, only one element can have keyboard focus, and the system has to decide which element receives the focus. When the second MyWidget is created, it receives the focus because it is the last element to set its focus property to true.

This problem is due to visibility. The MyWidget component would like to have the focus, but it cannot control the focus when it is imported or reused. Likewise, the window component does not have the ability to know if its imported components are requesting the focus.

To solve this problem, the QML introduces a concept known as a focus scope. For existing Qt users, a focus scope is like an automatic focus proxy. A focus scope is created by declaring the FocusScope element.

In the next example, a FocusScope element is added to the component, and the visual result shown.

  1.                 FocusScope  {
  3.     //FocusScope needs to bind to visual properties of the Rectangle
  4.     property alias color: rectangle.color
  5.     x: rectangle.x; y: rectangle.y
  6.     width: rectangle.width; height: rectangle.height
  8.     Rectangle  {
  9.         id: rectangle
  10.         anchors.centerIn: parent
  11.         color: "lightsteelblue"; width: 175; height: 25; radius: 10; smooth: true
  12.         Text  { id: label; anchors.centerIn: parent }
  13.         focus: true
  14.         Keys.onPressed:  {
  15.             if (event.key == Qt.Key_A)
  16.                 label.text = 'Key A was pressed'
  17.             else if (event.key == Qt.Key_B)
  18.                 label.text = 'Key B was pressed'
  19.             else if (event.key == Qt.Key_C)
  20.                 label.text = 'Key C was pressed'
  21.         }
  22.     }
  23. }

Conceptually focus scopes are quite simple.

  • Within each focus scope one element may have Item::focus set to true. If more than one Item has the focus property set, the last element to set the focus will have the focus and the others are unset, similar to when there are no focus scopes.
  • When a focus scope receives active focus, the contained element with focus set (if any) also gets the active focus. If this element is also a FocusScope, the proxying behavior continues. Both the focus scope and the sub-focused item will have activeFocus property set.

Note that, since the FocusScope element is not a visual element, the properties of its children need to be exposed to the parent item of the FocusScope. Layouts and positioning elements will use these visual and styling properties to create the layout. In our example, the Column element cannot display the two widgets properly because the FocusScope lacks visual properties of its own. The MyWidget component directly binds to the rectangle properties to allow the Column element to create the layout containing the children of the FocusScope.

So far, the example has the second component statically selected. It is trivial now to extend this component to make it clickable, and add it to the original application. We still set one of the widgets as focused by default. Now, clicking either MyClickableWidget gives it focus and the other widget loses the focus.

The code that imports and creates two MyClickableWidget instances:

  1.                 Rectangle  {
  2.     id: window
  4.     color: "white"; width: 240; height: 150
  6.     Column  {
  7.         anchors.centerIn: parent; spacing: 15
  9.         MyClickableWidget  {
  10.             focus: true             //set this MyWidget to receive the focus
  11.             color: "lightblue"
  12.         }
  13.         MyClickableWidget  {
  14.             color: "palegreen"
  15.         }
  16.     }
  18. }

The MyClickableWidget code:

  1.                 FocusScope  {
  3.     id: scope
  5.     //FocusScope needs to bind to visual properties of the children
  6.     property alias color: rectangle.color
  7.     x: rectangle.x; y: rectangle.y
  8.     width: rectangle.width; height: rectangle.height
  10.     Rectangle  {
  11.         id: rectangle
  12.         anchors.centerIn: parent
  13.         color: "lightsteelblue"; width: 175; height: 25; radius: 10; smooth: true
  14.         Text  { id: label; anchors.centerIn: parent }
  15.         focus: true
  16.         Keys.onPressed:  {
  17.             if (event.key == Qt.Key_A)
  18.                 label.text = 'Key A was pressed'
  19.             else if (event.key == Qt.Key_B)
  20.                 label.text = 'Key B was pressed'
  21.             else if (event.key == Qt.Key_C)
  22.                 label.text = 'Key C was pressed'
  23.         }
  24.     }
  25.     MouseArea  { anchors.fill: parent; onClicked:  { scope.focus = true } }
  26. }

When a QML Item explicitly relinquishes focus (by setting its focus property to false while it has active focus), the system does not automatically select another element to receive focus. That is, it is possible for there to be no currently active focus.

See the Keyboard Focus example for a demonstration of moving keyboard focus between multiple areas using FocusScope elements.

Advanced uses of Focus Scopes

Focus scopes allow focus to allocation to be easily partitioned. Several QML items use it to this effect.

ListView, for example, is itself a focus scope. Generally this isn't noticeable as ListView doesn't usually have manually added visual children. By being a focus scope, ListView can focus the current list item without worrying about how that will effect the rest of the application. This allows the current item delegate to react to key presses.

This contrived example shows how this works. Pressing the Return key will print the name of the current list item.

  1.                 Rectangle  {
  2.     color: "lightsteelblue"; width: 100; height: 50
  4.     ListView  {
  5.         anchors.fill: parent
  6.         focus: true
  8.         model: ListModel  {
  9.             ListElement  { name: "Bob" }
  10.             ListElement  { name: "John" }
  11.             ListElement  { name: "Michael" }
  12.         }
  14.         delegate: FocusScope  {
  15.                 width: childrenRect.width; height: childrenRect.height
  16.                 x:childrenRect.x; y: childrenRect.y
  17.                 TextInput  {
  18.                     focus: true
  19.                     text: name
  20.                     Keys.onReturnPressed: console.log(name)
  21.                 }
  22.         }
  23.     }
  24. }

While the example is simple, there are a lot going on behind the scenes. Whenever the current item changes, the ListView sets the delegate's Item::focus property. As the ListView is a focus scope, this doesn't affect the rest of the application. However, if the ListView itself has active focus this causes the delegate itself to receive active focus. In this example, the root element of the delegate is also a focus scope, which in turn gives active focus to the Text element that actually performs the work of handling the Return key.

All of the QML view classes, such as PathView and GridView, behave in a similar manner to allow key handling in their respective delegates.

Notes provided by the Qt Community

No notes