Focus-Handling Methods for Qt Quick StackViews

Article summary

On my current project, we’re building the GUI in Qt 5. It’s (mostly) open-source, has some really intriguing platform support, and Qt Quick 2 has a fairly advanced model for both keyboard focus and transitioning focus between widgets just with the keyboard.

When I started work on a spike to prove out some ideas I had about using a Qt Quick StackView to structure the navigation of our app, I still managed to run into some problems with transitioning focus between widgets. Read on for my solution.

The Problem

Our application is going to have a fair number of ListViews throughout, some of which will provide context for navigation changes. I thought that using a StackView as the primary organization of our application would be a decent idea. Not only would we get some decent history semantics, we’d also get transitions for free.

Unfortunately, I quickly realized that active focus wasn’t following new views as they were pushed onto the stack view. Key events were still being captured by the previously-focused widget.

The Solution

In the end, the solution wound up being exceptionally simple. Qt Quick provides hooks into QObject property changes, similar to several JavaScript frameworks I’ve used previously. Simply by adding a trigger on the “currentItem” property of the stack view, I was able to choose a new UI element to receive focus as the currently-active view changed.

The following is a somewhat contrived example. Please don’t actually structure your app like this.


# AppStackItem.qml
import QtQuick 2.4

Rectangle {
  property Item defaultFocusItem: this
}

# main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3

StackView {
  id: mainStack
  initialItem: firstItem
  anchors.fill: parent
  focus: true
  onCurrentItemChanged: {
    if (currentItem) {
      currentItem.defaultFocusItem.focus = true
    }
  }


  AppStackItem {
    id: firstItem
    visible: true
    color: "black"
    defaultFocusItem: viewOneList
    ListView {
      id: viewOneList
      anchors.fill: parent
      model: ListModel {
        ListElement {
          itemText: "foo"
        }
        ListElement {
          itemText: "bar"
        }
        ListElement {
          itemText: "baz"
        }
      }
      focus: true
      delegate: Component {
        Item {
          height: contentItem.height + 10
          anchors.left: parent.left
          anchors.right: parent.right
          Keys.onReturnPressed: {
            mainStack.push(secondItem)
          }
          Text {
            id: contentItem
            font.pixelSize: 40
            text: itemText
            color: "white"
          }
        }
      }
      highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
    }
  }

  AppStackItem {
    id: secondItem
    visible: false
    color: "black"
    defaultFocusItem: viewTwoList
    ListView {
      id: viewTwoList
      anchors.fill: parent
      highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
      focus: true
      model: ListModel {
        ListElement {
          itemText: "qux"
        }
        ListElement {
          itemText: "quux"
        }
      }
      delegate: Component {
        Item {
          height: contentItem.height + 10
          anchors.left: parent.left
          anchors.right: parent.right
          Keys.onEscapePressed: {
            mainStack.pop()
          }
          Text {
            id: contentItem
            font.pixelSize: 40
            text: itemText
            color: "white"
          }
        }
      }
    }
  }
}

Using this method and the KeyNavigation integration already built into Qt Quick, I’ve been able to implement complete UI navigation with just four buttons.

What neat QML tricks have you discovered lately?

Conversation
  • Amalya says:

    Thanks a lot! i struggled with this problem for quit some time and found some solutions that did not work 100% of the time. Now it’s perfect!!

  • Comments are closed.