Menu
SectorenAutomotiveConsumer electronicsIndustrial automationMedicalPlatformenTechnologyOver onsBlogContact
Invisto nv
Voetbalstraat 17
8870 Izegem
QML

QML Keyboard

Bob Messiaen
Bob Messiaen
1.4.2023
QML Keyboard

1. Intro

A virtual keyboard is needed when there is no physical keyboard available. Most often this is the case for electronic devices using a touchscreen.

For Qt applications, there are a couple ways how you can add a virtual keyboard. Typically one these will be used:

  • Using the Qt Virtual Keyboard
  • Utilizing the keyboard provided by the system platform, such as the Apple keyboard on an iPad.
  • Creating a custom-build QML keyboard.
Invisto frequently employs the custom-built QML keyboard method, as we do not want to impose any restrictions on design or usability. This is supported by the fact that QML actually allows you to reproduce a graphic design very easily.

2. Keyboard

keyboard

In this article, we will be focusing on a keyboard designed for editing patient information. We will examine how you can:

  • Setup basic keyboard layout.
  • Connecting the keyboard to a text field.
  • How to dynamically change the keyboard buttons when switching from alphabetical to caps.

2.1 Keyboard Layout

For this application we did not consider a responsive design layout, meaning that the screen dimensions are fixed.

 
import QtQuick
import QtQuick.Controls

Item {
    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 have used a flexible grid by combining a Column and multiple Rows. Each Row contains a set of keyboard buttons.

 
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: "3"; second: "e" }
        ListElement { first: "4"; second: "r" }
        ListElement { first: "5"; second: "t" }
        ListElement { first: "6"; second: "y" }
        ListElement { first: "7"; second: "u" }
        ListElement { first: "8"; second: "i" }
        ListElement { first: "9"; second: "o" }
        ListElement { first: "0"; second: "p" }
    }
    
    ListModel {
        id: firstRowAlphabeticalCaps
        
        ListElement { first: "1"; second: "Q" }
        ListElement { first: "2"; second: "W" }
        ListElement { first: "3"; second: "E" }
        ListElement { first: "4"; second: "R" }
        ListElement { first: "5"; second: "T" }
        ListElement { first: "6"; second: "Y" }
        ListElement { first: "7"; second: "U" }
        ListElement { first: "8"; second: "I" }
        ListElement { first: "9"; second: "O" }
        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 examine how buttons are built. In our scenario, a KeyboardButton is simply a rectangular shape with some styling.

 
// KeyboardButton.qml
import QtQuick

Rectangle {
    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 display the desired content on top of the button, we can add a child element to it:

  • SingleLabel
  • SingleIcon
  • DoubleLabel
 
KeyboardButton {
    dark: true
    width: 120
    
    SingleLabel {
        alignLeft: true
        text: ".?123"
    }
}

2.2 Connecting the keyboard to a text field.

To demonstrate the connection between a keyboard and an editable text field, we have simplified our EditText.qml for this tutorial by removing extraneous elements and retaining only the pertinent components. Essentially, we have taken a QML TextInput with visual upgrades and placed a MouseArea on top of it.

 
// EditText.qml
import QtQuick
import QtQuick.Controls

Item {
    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
        }
    }
}

So what happens when a text field is clicked?

  • We set the focus on our text input. By doing so, the cursor becomes visible.
  • We inform the keyboard which text input has received the focus => keyboard.focusedTextField = root

Now on the keyboard side, when a keyboard button is clicked?

 
// DoubleLabel.qml
import QtQuick

Item {
    id: root

    anchors.fill: parent

    property string first: ""
    property string second: ""

    Text {
        text: root.first
        anchors.horizontalCenter: root.horizontalCenter
        y: 9

        font.pixelSize: 17
        font.family: theme.fontRegular
        color: Qt.rgba(235, 235, 245, 0.3)
    }

    Text {
        text: root.second
        anchors.horizontalCenter: root.horizontalCenter
        y: 31

        font.pixelSize: 28
        font.family: theme.fontRegular
        color: "#fff"
    }

    MouseArea {
        id: mouseArea
        anchors.fill: root
        propagateComposedEvents: true
        onPressed: {
            keyboardRoot.handleButtonPressed(root.second)
            mouse.accepted = false
        }
    }
}

The content items (SingleIcon, SingleLabel, DoubleLabel) each call the keyboards' handleButtonPressed(label) function.

In this handler, the various actions like adding a character, removing a character are taken care off.

 
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 === "tab")
    {
        keyboardRoot.focusedTextField.text = keyboardRoot.focusedTextField.text + "\t"
        keyboardRoot.focusedTextField.cursorPosition = keyboardRoot.focusedTextField.text.length
    }
    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 if (button === "")
    {
        keyboardRoot.focusedTextField.text = keyboardRoot.focusedTextField.text + " "
        keyboardRoot.focusedTextField.cursorPosition = keyboardRoot.focusedTextField.text.length
    }
    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 alphabetical and caps

We have defined two models for each Row:

  • firstRowAlphabetical
  • firstRowAlphabeticalCaps

Depending on the mode of the keyboard we pass one of the models to the Repeaters model property.

 
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: "3"; second: "e" }
        ListElement { first: "4"; second: "r" }
        ListElement { first: "5"; second: "t" }
        ListElement { first: "6"; second: "y" }
        ListElement { first: "7"; second: "u" }
        ListElement { first: "8"; second: "i" }
        ListElement { first: "9"; second: "o" }
        ListElement { first: "0"; second: "p" }
    }
    
    ListModel {
        id: firstRowAlphabeticalCaps
        
        ListElement { first: "1"; second: "Q" }
        ListElement { first: "2"; second: "W" }
        ListElement { first: "3"; second: "E" }
        ListElement { first: "4"; second: "R" }
        ListElement { first: "5"; second: "T" }
        ListElement { first: "6"; second: "Y" }
        ListElement { first: "7"; second: "U" }
        ListElement { first: "8"; second: "I" }
        ListElement { first: "9"; second: "O" }
        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
        }
    }
}

3. Qt WebBrowser as a requirement? No Pasaran!

The implementation of a custom-build QML keyboard, as described in this post, does not meet the requirements if you use html <input> fields in combination with Qt WebBrowser. In such cases, the approach becomes more complex, and one may need to explore QInputMethod, QPlatformInputContext, QPA, and others.

Although this falls outside the scope of this post, it is certainly worth noting here.

4. Conclusion

Having a nice looking, functional keyboard tailored to your needs requires a custom-build QML keyboard. The purpose of this post is to provide you with an introduction on how to get started with this.

If you need more info on this topic, feel free to contact us. We didn't share the full source code here since it's part of a project. But we can always send you a working example if needed.

Ook interesse in een state-of-the-art interface die uw product naar een hoger niveau tilt?

Let's talk!

Enkele van onze klanten

Logo HoneywellLogo Picanol GroupLogo CiscoLogo BekaertLogo Universiteit GentLogo Renson