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.