Invisto
Invisto · your interface partner
Emailinfo@invisto.bePhone+32 477 42 11 14OfficeHangar K, Kortrijknlen
All insights
insights/QML

How to build a virtual QML keyboard

How to build a virtual QML keyboard

1. Introduction

A virtual keyboard is needed when no physical keyboard is available. This is usually the case for electronic devices with a touchscreen.

There are several ways to add a virtual keyboard to a Qt application. Usually one of the following options is used:

  • The Qt Virtual Keyboard
  • The keyboard provided by the platform itself, such as the Apple keyboard on an iPad
  • A custom-built QML keyboard

At Invisto we often go with the custom-built QML approach, because we do not want to impose any limitations on design or usability. It also helps that QML makes it very easy to reproduce a graphic design.

2. Keyboard

The keyboard for editing patient information
The keyboard for editing patient information

In this article we focus on a keyboard designed for editing patient information. We look at how to:

  • Set up a basic keyboard layout
  • Connect the keyboard to a text field
  • Change keys dynamically when switching between lowercase and uppercase

2.1 Keyboard layout

For this application we do not need a responsive design: the screen dimensions are fixed.

QML
import QtQuickimport QtQuick.ControlsItem {    id: keyboardRoot    width: 1194    height: 418    property var focusedTextField: null    enum Modes {        HIDDEN,        ALPHABETICAL,        ALPHABETICAL_CAPS,        NUMBERS_1,        NUMBERS_2    }    QtObject {        id: internals        property bool singleShift: false    }    property int mode: Keyboard.ALPHABETICAL    Item { id: keyboardHeader ... }    Column {        id: mainButtons        Row { id: buttonsFirstRow ... }        Row { id: buttonsSecondRow ... }        Row { id: buttonsThirdRow ... }        Row { id: buttonsFourthRow ... }    }    Rectangle { id: horizontalLine ... }}

As you can see, we use a flexible grid structure by combining a column with several rows. Each row contains a set of keyboard buttons.

QML
Row {    id: buttonsFirstRow    spacing: 12    KeyboardButton {        dark: true        width: 105        SingleLabel { text: "tab" }    }    ListModel {        id: firstRowAlphabetical        ListElement { first: "1"; second: "q" }        ListElement { first: "2"; second: "w" }        ...        ListElement { first: "0"; second: "p" }    }    ListModel {        id: firstRowAlphabeticalCaps        ListElement { first: "1"; second: "Q" }        ListElement { first: "2"; second: "W" }        ...        ListElement { first: "0"; second: "P" }    }    Repeater {        model: keyboardRoot.mode === Keyboard.ALPHABETICAL ? firstRowAlphabetical : firstRowAlphabeticalCaps        KeyboardButton {            DoubleCharacters {                first: model.first                second: model.second            }        }    }    KeyboardButton {        dark: true        width: 105        SingleLabel { text: "delete"; alignLeft: false }    }}

Let’s take a look at how the buttons are built. In our scenario a button is simply a rectangle with some styling.

QMLKeyboardButton.qml
import QtQuickRectangle {    id: root    property bool dark: false    color: dark ? "#45454B" : "#7C7C86"    width: 82    height: 74    radius: 7    border.color: "#222"    MouseArea {        id: mouseArea        anchors.fill: root        onPressed: root.border.width = 2        onReleased: root.border.width = 0    }}

To show the desired content on top of the button, we can add a child element such as SingleLabel, SingleIcon or DoubleLabel.

2.2 Connecting the keyboard to a text field

To demonstrate how a keyboard connects to an editable text field, we simplified our EditText.qml by removing unnecessary elements and keeping only the relevant parts. In essence it is a QML TextInput with some visual enhancements, with a MouseArea on top.

QMLEditText.qml
import QtQuickimport QtQuick.ControlsItem {    id: root    property string title: ""    property string text: ""    property alias cursorPosition: textInputField.cursorPosition    width: 550    height: 88    TextInput {        id: textInputField        text: root.text        color: "white"        font.family: theme.fontRegular        font.pixelSize: 18        anchors.verticalCenter: textBox.verticalCenter        x: 27    }    MouseArea {        anchors.fill: root        onClicked: {            textInputField.focus = true            keyboard.focusedTextField = root        }    }}

What happens when you tap a text field?

  • We give focus to our TextInput. This makes the cursor visible.
  • We tell the keyboard which text field has received focus: keyboard.focusedTextField = root

What happens on the keyboard side when a button is pressed? The content elements (SingleIcon, SingleLabel, DoubleLabel) each call the function keyboard.handleButtonPressed(label). This handler takes care of actions such as adding or removing a character.

QML
function handleButtonPressed(button){    if (button === "delete") {        var str = keyboardRoot.focusedTextField.text        if (str.length) {            str = str.substring(0, str.length - 1);            keyboardRoot.focusedTextField.text = str            keyboardRoot.focusedTextField.cursorPosition = str.length        }    }    else if (button === "caps lock") {        if (keyboardRoot.mode === Keyboard.ALPHABETICAL)            keyboardRoot.mode = Keyboard.ALPHABETICAL_CAPS        else if (keyboardRoot.mode === Keyboard.ALPHABETICAL_CAPS)            keyboardRoot.mode = Keyboard.ALPHABETICAL    }    else if (button === "shift") {        if (keyboardRoot.mode === Keyboard.ALPHABETICAL) {            keyboardRoot.mode = Keyboard.ALPHABETICAL_CAPS            internals.singleShift = true        }    }    else if (button === ".?123") {        keyboardRoot.mode = Keyboard.NUMBERS_1    }    else if (button === "return") {        keyboardRoot.mode = Keyboard.HIDDEN    }    else {        keyboardRoot.focusedTextField.text = keyboardRoot.focusedTextField.text + button        keyboardRoot.focusedTextField.cursorPosition = keyboardRoot.focusedTextField.text.length        if (internals.singleShift) {            internals.singleShift = false            keyboardRoot.mode = Keyboard.ALPHABETICAL        }    }}

2.3 Switching between lowercase and uppercase

For each row we defined two models: firstRowAlphabetical and firstRowAlphabeticalCaps. Depending on the keyboard mode, we pass one of these models to the Repeater’s model property (see the snippet in 2.1).

3. Qt WebBrowser as a requirement? No pasaran!

The custom-built QML implementation described in this article falls short when you use HTML fields in combination with Qt WebBrowser. In that case the approach gets more complex, and you may need to look at QInputMethod, QPlatformInputContext, QPA and other components. That is outside the scope of this case, but worth mentioning.

4. Conclusion

A good-looking, functional keyboard that is fully tailored to your needs calls for a custom-built QML keyboard. This article is meant as an introduction to help you get started.

Need more information on this topic? Feel free to get in touch. We do not share the full source code here, because it is part of a project. We can always provide a working example if you would like one.

Interested in an interface that takes your product to the next level?

Get in touch