Hoe bouw je een virtueel QML toetsenbord?

1. Introductie
Een virtueel toetsenbord is nodig wanneer er geen fysiek toetsenbord beschikbaar is. Meestal is dit het geval bij elektronische apparaten met een touchscreen.
Voor Qt-applicaties zijn er verschillende manieren om een virtueel toetsenbord toe te voegen. Doorgaans wordt één van de volgende opties gebruikt:
- Het Qt Virtual Keyboard
- Het toetsenbord dat door het platform zelf wordt voorzien, zoals het Apple-toetsenbord op een iPad
- Een custom-built QML-toetsenbord
Invisto maakt vaak gebruik van de custom-built QML-methode, omdat we geen beperkingen willen opleggen op vlak van design of gebruiksgemak. Dit wordt bovendien ondersteund door het feit dat je met QML een grafisch ontwerp heel eenvoudig kunt reproduceren.
2. Toetsenbord
In dit artikel focussen we op een toetsenbord dat ontworpen is voor het bewerken van patiënteninformatie. We bekijken hoe je:
- Een basis lay-out voor een toetsenbord opzet
- Het toetsenbord koppelt aan een tekstveld
- Toetsen dynamisch verandert bij het schakelen tussen alfabetisch en hoofdletters
2.1 Lay-out van het toetsenbord
Voor deze toepassing houden we geen rekening met een responsive design: de schermdimensies zijn vast.
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 ... }}Zoals je kunt zien, gebruiken we een flexibele gridstructuur door een kolom te combineren met meerdere rijen. Elke rij bevat een reeks toetsenbordknoppen.
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 } }}Laten we eens kijken hoe de knoppen zijn opgebouwd. In ons scenario is een knop gewoon een rechthoekige vorm met wat styling.
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 }}Om de gewenste inhoud boven op de knop weer te geven, kunnen we een child-element toevoegen, zoals SingleLabel, SingleIcon of DoubleLabel.
2.2 Het toetsenbord verbinden met een tekstveld
Om de koppeling tussen een toetsenbord en een bewerkbaar tekstveld te demonstreren, hebben we onze EditText.qml vereenvoudigd door overbodige elementen te verwijderen en enkel de relevante onderdelen te behouden. In essentie hebben we een QML TextInput met visuele verbeteringen, met daar bovenop een MouseArea.
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 } }}Wat gebeurt er wanneer je op een tekstveld klikt?
- We geven de focus aan onze TextInput. Hierdoor wordt de cursor zichtbaar.
- We informeren het toetsenbord welk tekstveld de focus heeft gekregen: keyboard.focusedTextField = root
Wat gebeurt er aan de kant van het toetsenbord wanneer een knop wordt ingedrukt? De inhoudselementen (SingleIcon, SingleLabel, DoubleLabel) roepen elk de functie keyboard.handleButtonPressed(label) aan. In deze handler worden acties afgehandeld zoals een karakter toevoegen of verwijderen.
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 Wisselen tussen alfabetisch en hoofdletters
Voor elke rij hebben we twee modellen gedefinieerd: firstRowAlphabetical en firstRowAlphabeticalCaps. Afhankelijk van de modus van het toetsenbord geven we één van deze modellen door aan de model-property van de Repeater (zie het fragment in 2.1).
3. Qt WebBrowser als vereiste? No pasaran!
De custom-built QML-implementatie die in dit artikel wordt beschreven, voldoet niet aan de vereisten wanneer je HTML -velden gebruikt in combinatie met Qt WebBrowser. In dat geval wordt de aanpak complexer en moet je mogelijk kijken naar QInputMethod, QPlatformInputContext, QPA en andere componenten. Hoewel dit buiten de scope van deze case valt, is het wel de moeite waard om te vermelden.
4. Conclusie
Een mooi en functioneel toetsenbord dat volledig op jouw noden is afgestemd, vereist een custom-built QML-toetsenbord. Dit artikel wil je een introductie geven over hoe je hiermee kunt starten.
Heb je meer info nodig over dit onderwerp? Neem gerust contact met ons op. We delen hier niet de volledige broncode, omdat die deel uitmaakt van een project. Maar we kunnen je altijd een werkend voorbeeld bezorgen indien gewenst.
