Meego Wiki
Views

QML/Keyboard navigation

From MeeGo wiki
(Difference between revisions)
Jump to: navigation, search
(Adding key navigation)
m (+cats)
 
(16 intermediate revisions not shown)
Line 2: Line 2:
This tutorial explains how to make a QML user interface entirely navigable by keyboard.
This tutorial explains how to make a QML user interface entirely navigable by keyboard.
 +
 +
Note that I've used the basic QML elements for this interface, and not employed any widget sets. It would also make sense to encapsulate this behaviour in components etc. for a full application (e.g. as done in [http://qt.gitorious.org/qt-components/qt-components/trees/master/src/MeeGo the Qt components for MeeGo]).
== Pre-requisites ==  
== Pre-requisites ==  
Line 17: Line 19:
You basically need to create a QML application with some input elements to start with.
You basically need to create a QML application with some input elements to start with.
-
Then add keyboard navigation using the following QML elements:
+
Then add keyboard navigation using <code>KeyNavigation</code> elements, defining paths between pairs of UI elements (e.g. to move between input elements).
-
* <code>KeyNavigation</code>, for defining paths between pairs of UI elements (e.g. to move between input elements)
+
Finally use <code>Keys</code> elements, to connect key presses to actions (e.g. to submit data entered in input elements).
-
* <code>Keys</code>, for connecting key presses to actions (e.g. to submit data entered in input elements)
+
=== Create a QML application with basic input elements ===
=== Create a QML application with basic input elements ===
Line 108: Line 109:
           radius: 5
           radius: 5
         }
         }
-
         delegate: Item {
+
         delegate: Text {
           width: parent.width
           width: parent.width
           anchors.horizontalCenter: parent.horizontalCenter
           anchors.horizontalCenter: parent.horizontalCenter
           height: 20
           height: 20
-
           Text { text: name }
+
           text: name
         }
         }
       }
       }
Line 280: Line 281:
           radius: 5
           radius: 5
         }
         }
-
         delegate: Item {
+
         delegate: Text {
           width: parent.width
           width: parent.width
           anchors.horizontalCenter: parent.horizontalCenter
           anchors.horizontalCenter: parent.horizontalCenter
           height: 20
           height: 20
-
           Text { text: name }
+
           text: name
         }
         }
Line 333: Line 334:
* A <code>focus</code> property set to <code>true</code> on the element which should receive the initial focus. In this case, it's the name entry field.
* A <code>focus</code> property set to <code>true</code> on the element which should receive the initial focus. In this case, it's the name entry field.
* Variable opacity on each <code>Row</code> element, dependent on whether the input element within the row has the <code>activeFocus</code> or not. This fades out any rows which don't contain an element in focus, and highlights the row which currently has the focus. Obviously, other approaches (using color, animations etc.) to highlight the active element are possible.
* Variable opacity on each <code>Row</code> element, dependent on whether the input element within the row has the <code>activeFocus</code> or not. This fades out any rows which don't contain an element in focus, and highlights the row which currently has the focus. Obviously, other approaches (using color, animations etc.) to highlight the active element are possible.
 +
 +
More documentation on QML <code>KeyNavigation</code>: http://doc.qt.nokia.com/4.7/qml-keynavigation.html
 +
 +
<code>KeyNavigation</code> is very convenient but limited, as it only recognises the arrow keys. If you want to trigger shifts of focus on other key presses, you will need to use the <code>Keys</code> element, described next.
 +
 +
=== Triggering actions on key events ===
 +
 +
The <code>Keys</code> element is used to associate key press events on a UI element with some action. In the case of our keyboard-navigable UI, we'll use this to "submit" the data entered in the form and use it to show the user a greeting.
 +
 +
The basic pattern for using <code>Keys</code> is to connect one of the <code>Keys.on*</code> signals to a function. There are signals for each of the common keys (e.g. <code>onReturnPressed</code>, <code>onAsteriskPressed</code>) as well as a generic signal which is emitted whenever any key is pressed on the element (<code>onPressed</code>).
 +
 +
The example below extends the two red rectangles example of the previous section, adding some actions based on key presses.
 +
 +
<pre>
 +
import Qt 4.7
 +
 +
Rectangle {
 +
  width: 200
 +
  height: 100
 +
 +
  Rectangle {
 +
    id: rect1
 +
    focus: true
 +
    width: 100
 +
    height: 100
 +
    opacity: activeFocus ? 1.0 : 0.5
 +
    color: "red"
 +
 +
    KeyNavigation.right: rect2
 +
 +
    Keys.onPressed: {
 +
      if (event.key != Qt.Key_Right) {
 +
        console.log(event.key + " pressed on left rectangle");
 +
        event.accepted = true;
 +
      }
 +
    }
 +
  }
 +
 +
  Rectangle {
 +
    id: rect2
 +
    width: 100
 +
    height: 100
 +
    x: 100
 +
    opacity: activeFocus ? 1.0 : 0.5
 +
    color: "red"
 +
    KeyNavigation.left: rect1
 +
 +
    Keys.onReturnPressed: {
 +
      console.log("Return pressed on right rectangle");
 +
    }
 +
  }
 +
}
 +
</pre>
 +
 +
A couple of things to note:
 +
 +
* The left-hand rectangle is configured to shift focus when the right arrow key is pressed. But it also has a handler for the catch-all <code>Keys.onPressed</code> signal handler. So we have to make the signal handler ignore right arrow key presses. We can do this by adding a conditional which looks at the key event and ignores it if the right arrow key was pressed, leaving it to propagate to other handlers. Every other key press logs the key value to the console and sets <code>event.accepted = true</code> to stop the event propagating to other handlers.
 +
* The right-hand rectangle is simpler, as we add a specific <code>Keys.onReturnPressed</code> handler which doesn't interfere with the navigation keys: it only responds to the ''Return'' key (by logging a message to the console). Handlers for more-specific key handlers don't have to set <code>event.accepted = true</code>, as this is implicit.
 +
* In both cases, the handler for the <code>Keys</code> signal is a block of JavaScript enclosed in curly brackets. This code can reference elements declared in the QML script, as we'll see in a moment.
 +
 +
It is possible to just use <code>Keys</code> handlers to do both navigation as well as other actions. For example, we could remove the <code>KeyNavigation</code> element from <code>rect1</code> and replace the <code>Keys.onPressed</code> handler with code which also does navigation:
 +
 +
<pre>
 +
Keys.onPressed: {
 +
  /* right key or tab key shift focus to rect2 */
 +
  if (event.key == Qt.Key_Tab || event.key == Qt.Key_Right) {
 +
    rect2.focus = true;
 +
  }
 +
  else {
 +
    console.log(event.key + " pressed on left rectangle");
 +
    event.accepted = true;
 +
  }
 +
}
 +
</pre>
 +
 +
We can now apply the same techniques to our simple UI so that the submit "button" responds to presses of the ''Return'' key by displaying a greeting. This is as simple as adding a handler to the <code>submitButton</code> element:
 +
 +
<pre>
 +
Rectangle {
 +
  id: submitButton
 +
 +
  width: parent.width
 +
  height: parent.height
 +
  color: "darkgrey"
 +
  radius: 5
 +
 +
  Text {
 +
    text: "Submit"
 +
    anchors.horizontalCenter: parent.horizontalCenter
 +
    anchors.verticalCenter: parent.verticalCenter
 +
  }
 +
 +
  KeyNavigation.left: countrySelector
 +
 +
  Keys.onReturnPressed: {
 +
    greeting.text = "Hello " + nameInput.text + ", hailing from " +
 +
                    countrySelector.currentItem.text + "!";
 +
  }
 +
}
 +
</pre>
 +
 +
The handler constructs a text string from the input elements and sets the text of the <code>greeting</code> element to the result.
 +
 +
Here's what the finished application looks like running on a MeeGo netbook under the standard Qt QML viewer:
 +
 +
[[File:Qml-key-navigation-app-on-meego.png]]
 +
 +
P.S. I'm not from America.
 +
 +
More documentation on QML <code>Keys</code>: http://doc.qt.nokia.com/4.7/qml-keys.html
 +
 +
A list of the available key constants: http://doc.qt.nokia.com/4.7/qt.html#Key-enum (note that you need to use . instead of :: when you are referencing these constants from QML)
 +
 +
[[Category:tutorial]]
 +
[[Category:qml]]

Latest revision as of 08:45, 31 December 2010

Contents

Overview

This tutorial explains how to make a QML user interface entirely navigable by keyboard.

Note that I've used the basic QML elements for this interface, and not employed any widget sets. It would also make sense to encapsulate this behaviour in components etc. for a full application (e.g. as done in the Qt components for MeeGo).

Pre-requisites

If you don't know anything about QML, reading the introductory Qt/QML documentation might be helpful.

System Setup

Install the MeeGo SDK first. Alternatively, you could just install the meego-sdk-qt-creator and meego-sdk-qt packages (see the same page for the location of the repos).

I'm using Fedora 13 Linux, and have run the application on Fedora 13 and MeeGo 1.1.

How to

You basically need to create a QML application with some input elements to start with.

Then add keyboard navigation using KeyNavigation elements, defining paths between pairs of UI elements (e.g. to move between input elements).

Finally use Keys elements, to connect key presses to actions (e.g. to submit data entered in input elements).

Create a QML application with basic input elements

  1. In the Qt Creator Welcome screen, click Create Project.
  2. Select Qt Quick Project and QML Application, then click Choose.
  3. Enter a name for the project (e.g. key-nav), then click Next and Finish.
  4. Edit the key-nav.qml file, which contains the main UI definition. The first version just displays three input elements:
    • A single line text edit box for a name.
    • A list of countries a user can select from.
    • A button to submit the input data.
    • A label to display a greeting message based on the name and selected country.

    This is what the QML looks like for these elements:

    import Qt 4.7
    
    Rectangle {
      id: window
      width: 600
      height: 300
    
      ListModel {
        id: countries
    
        ListElement {
          name: "Finland"
        }
        ListElement {
          name: "United Kingdom"
        }
        ListElement {
          name: "USA"
        }
      }
    
      Column {
        anchors.left: parent.left
        anchors.leftMargin: 10
    
        anchors.top: parent.top
        anchors.topMargin: 10
    
        spacing: 20
        width: 180
    
        Row {
          spacing: 10
          width: parent.width
    
          Text {
            text: "Name:"
          }
    
          TextInput {
            id: nameInput
    
            maximumLength: 30
            width: maximumLength * 10
            focus: true
            fillColor: "darkgrey"
          }
        }
    
        Row {
          spacing: 10
          height: countries.count * 20
          width: parent.width
    
          Text {
            text: "Country:"
          }
    
          ListView {
            id: countrySelector
    
            width: parent.width
            height: parent.height
            model: countries
            highlight: Rectangle {
              color: "darkgrey"
              radius: 5
            }
            delegate: Text {
              width: parent.width
              anchors.horizontalCenter: parent.horizontalCenter
              height: 20
              text: name
            }
          }
        }
    
        Row {
          width: parent.width
          height: 20
    
          Rectangle {
            id: submitButton
    
            width: parent.width
            height: parent.height
            color: "darkgrey"
            radius: 5
    
            Text {
              text: "Submit"
              anchors.horizontalCenter: parent.horizontalCenter
              anchors.verticalCenter: parent.verticalCenter
            }
          }
        }
    
        Row {
          width: parent.width
          height: 20
    
          Text {
            id: greeting
            text: "the greeting will be here"
          }
        }
      }
    }
    

    (I made no attempt at modularisation here, but kept everything in one file to make the explanations simpler. Also, this isn't usable at all yet.)

  5. Run the application using Qt Creator's qmlviewer (big green button, bottom left in Qt Creator). You should see something like this:
    Qml-key-navigation-basic-ui.png

Adding key navigation

Navigating from one QML element to another means that you shift the focus from the currently-focused element to another element. To do this, you add a KeyNavigation property to the element you're navigating from, associated with the arrow key (up, down, left, right) which will trigger the focus shift.

For example, say you had two rectangles, rect1 and rect2. You want a user to be able to navigate from rect1 to rect2 by pressing the right arrow key. To implement this, you would add the following property to rect1:

KeyNavigation.right: rect2

Which means "on pressing the right arrow key, shift the focus to the element with id rect2.

Key navigation is not symmetrical by default, so if you wanted to be able to go back to rect1 from rect2 by pressing the left arrow key, you need to add a property to rect2 as well:

KeyNavigation.left: rect1

Here's a complete example:

import Qt 4.7

Rectangle {
  width: 200
  height: 100

  Rectangle {
    id: rect1
    focus: true
    width: 100
    height: 100
    opacity: activeFocus ? 1.0 : 0.5
    color: "red"
    KeyNavigation.right: rect2
  }

  Rectangle {
    id: rect2
    width: 100
    height: 100
    x: 100
    opacity: activeFocus ? 1.0 : 0.5
    color: "red"
    KeyNavigation.left: rect1
  }
}

You can navigate from the left-hand red square (rect1) to the right-hand one by pressing the right cursor key; and from the right-hand red square to the left-hand one by pressing the left cursor key. When the focus shifts, the opacity of the focused element is set to 1.0, and the unfocused one to 0.5.

Applying the same principle to our simple UI, we get:

import Qt 4.7

Rectangle {
  id: window
  width: 600
  height: 300

  ListModel {
    id: countries

    ListElement {
      name: "Finland"
    }
    ListElement {
      name: "United Kingdom"
    }
    ListElement {
      name: "USA"
    }
  }

  Column {
    anchors.left: parent.left
    anchors.leftMargin: 10

    anchors.top: parent.top
    anchors.topMargin: 10

    spacing: 20
    width: 180

    Row {
      spacing: 10
      width: parent.width
      opacity: nameInput.activeFocus ? 1.0 : 0.25

      Text {
        text: "Name:"
      }

      TextInput {
        id: nameInput

        maximumLength: 30
        width: maximumLength * 10
        focus: true
        fillColor: "darkgrey"

        KeyNavigation.right: countrySelector
      }
    }

    Row {
      spacing: 10
      height: countries.count * 20
      width: parent.width
      opacity: countrySelector.activeFocus ? 1.0 : 0.25

      Text {
        text: "Country:"
      }

      ListView {
        id: countrySelector

        width: parent.width
        height: parent.height
        model: countries
        highlight: Rectangle {
          color: "darkgrey"
          radius: 5
        }
        delegate: Text {
          width: parent.width
          anchors.horizontalCenter: parent.horizontalCenter
          height: 20
          text: name
        }

        KeyNavigation.left: nameInput
        KeyNavigation.right: submitButton
      }
    }

    Row {
      width: parent.width
      height: 20
      opacity: submitButton.activeFocus ? 1.0 : 0.25

      Rectangle {
        id: submitButton

        width: parent.width
        height: parent.height
        color: "darkgrey"
        radius: 5

        Text {
          text: "Submit"
          anchors.horizontalCenter: parent.horizontalCenter
          anchors.verticalCenter: parent.verticalCenter
        }

        KeyNavigation.left: countrySelector
      }
    }

    Row {
      width: parent.width
      height: 20

      Text {
        id: greeting
        text: "the greeting will be here"
      }
    }
  }
}

The differences between this version and the previous one are:

  • KeyNavigation properties on each of the input elements. Note that I've used left and right arrow keys for navigation, as the up and down arrow keys default to changing the selected item in a ListView, as used for the country selector.
  • A focus property set to true on the element which should receive the initial focus. In this case, it's the name entry field.
  • Variable opacity on each Row element, dependent on whether the input element within the row has the activeFocus or not. This fades out any rows which don't contain an element in focus, and highlights the row which currently has the focus. Obviously, other approaches (using color, animations etc.) to highlight the active element are possible.

More documentation on QML KeyNavigation: http://doc.qt.nokia.com/4.7/qml-keynavigation.html

KeyNavigation is very convenient but limited, as it only recognises the arrow keys. If you want to trigger shifts of focus on other key presses, you will need to use the Keys element, described next.

Triggering actions on key events

The Keys element is used to associate key press events on a UI element with some action. In the case of our keyboard-navigable UI, we'll use this to "submit" the data entered in the form and use it to show the user a greeting.

The basic pattern for using Keys is to connect one of the Keys.on* signals to a function. There are signals for each of the common keys (e.g. onReturnPressed, onAsteriskPressed) as well as a generic signal which is emitted whenever any key is pressed on the element (onPressed).

The example below extends the two red rectangles example of the previous section, adding some actions based on key presses.

import Qt 4.7

Rectangle {
  width: 200
  height: 100

  Rectangle {
    id: rect1
    focus: true
    width: 100
    height: 100
    opacity: activeFocus ? 1.0 : 0.5
    color: "red"

    KeyNavigation.right: rect2

    Keys.onPressed: {
      if (event.key != Qt.Key_Right) {
        console.log(event.key + " pressed on left rectangle");
        event.accepted = true;
      }
    }
  }

  Rectangle {
    id: rect2
    width: 100
    height: 100
    x: 100
    opacity: activeFocus ? 1.0 : 0.5
    color: "red"
    KeyNavigation.left: rect1

    Keys.onReturnPressed: {
      console.log("Return pressed on right rectangle");
    }
  }
}

A couple of things to note:

  • The left-hand rectangle is configured to shift focus when the right arrow key is pressed. But it also has a handler for the catch-all Keys.onPressed signal handler. So we have to make the signal handler ignore right arrow key presses. We can do this by adding a conditional which looks at the key event and ignores it if the right arrow key was pressed, leaving it to propagate to other handlers. Every other key press logs the key value to the console and sets event.accepted = true to stop the event propagating to other handlers.
  • The right-hand rectangle is simpler, as we add a specific Keys.onReturnPressed handler which doesn't interfere with the navigation keys: it only responds to the Return key (by logging a message to the console). Handlers for more-specific key handlers don't have to set event.accepted = true, as this is implicit.
  • In both cases, the handler for the Keys signal is a block of JavaScript enclosed in curly brackets. This code can reference elements declared in the QML script, as we'll see in a moment.

It is possible to just use Keys handlers to do both navigation as well as other actions. For example, we could remove the KeyNavigation element from rect1 and replace the Keys.onPressed handler with code which also does navigation:

Keys.onPressed: {
  /* right key or tab key shift focus to rect2 */
  if (event.key == Qt.Key_Tab || event.key == Qt.Key_Right) {
    rect2.focus = true;
  }
  else {
    console.log(event.key + " pressed on left rectangle");
    event.accepted = true;
  }
}

We can now apply the same techniques to our simple UI so that the submit "button" responds to presses of the Return key by displaying a greeting. This is as simple as adding a handler to the submitButton element:

Rectangle {
  id: submitButton

  width: parent.width
  height: parent.height
  color: "darkgrey"
  radius: 5

  Text {
    text: "Submit"
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.verticalCenter: parent.verticalCenter
  }

  KeyNavigation.left: countrySelector

  Keys.onReturnPressed: {
    greeting.text = "Hello " + nameInput.text + ", hailing from " +
                    countrySelector.currentItem.text + "!";
  }
}

The handler constructs a text string from the input elements and sets the text of the greeting element to the result.

Here's what the finished application looks like running on a MeeGo netbook under the standard Qt QML viewer:

Qml-key-navigation-app-on-meego.png

P.S. I'm not from America.

More documentation on QML Keys: http://doc.qt.nokia.com/4.7/qml-keys.html

A list of the available key constants: http://doc.qt.nokia.com/4.7/qt.html#Key-enum (note that you need to use . instead of :: when you are referencing these constants from QML)

Personal tools