Qt Virtual Keyboard 概述
功能
Qt Virtual Keyboard 的主要功能包括:
- 可自定义的键盘布局和样式,支持动态切换。
- 带单词选择功能的预测性文本输入。
- 字符预览和替代字符视图。
- 自动大写和空格插入。
- 可适应不同分辨率。
- 支持多种字符集(拉丁字母、简体/繁体中文、印地语、日语、阿拉伯语、希伯来语、韩语等)。
- 支持最常见的输入语言,并可轻松扩展语言支持。
- 支持从左到右和从右到左的输入。
- 支持硬件按键的双向和五向导航。
- 支持手写输入,并提供全屏输入的手势操作。
- 音频反馈。
- 跨平台功能。
- 同时支持Qt Quick 和Qt Widgets 应用程序。
支持的语言
虚拟键盘支持以下语言:
若要添加对其他语言的支持,请参阅“添加新的键盘布局”。
第三方插件
Qt Virtual Keyboard 支持以下供应商提供的第三方插件:
- Cerence XT9高级输入。
- Cerence 手写输入。
- MyScriptText 手写识别
《Qt Virtual Keyboard 构建指南》介绍了如何将这些插件集成到Qt Virtual Keyboard 中。
基本概念
Qt Virtual Keyboard 项目是一个 Qt 输入上下文插件,它实现了 QPlatformInputContextPlugin 和 QPlatformInputContext 接口。这些接口允许该插件在 Qt 应用程序中作为平台输入上下文插件使用。
该插件本身提供了一个支持多种输入法的输入框架,以及用于虚拟键盘的 QML 用户界面。该输入框架可通过插件接口进行扩展,从而允许在运行时加载第三方输入法和键盘布局。
该输入框架提供了以下主要接口:
- QVirtualKeyboardInputContext: 为虚拟键盘和其他输入组件提供上下文信息。作为底层文本输入组件的接口。
- QVirtualKeyboardInputEngine: 提供用于集成用户输入事件(按键等)的 API,并作为输入法的主机。
- QVirtualKeyboardAbstractInputMethod:基于 C++ 的输入法的基类。输入法通常处理键盘事件,但也可处理鼠标和触摸输入事件。
- InputMethod:基于 QML 的输入法的基类。输入法通常处理键盘事件,但也可以处理鼠标和触摸输入事件。
输入上下文
输入上下文既被键盘使用,也被具体的输入法使用。InputContext 是由 QML 托管的单例实例。应用程序不应直接与输入上下文交互。
上下文信息
输入上下文提供对源自应用程序的上下文信息的访问。这些信息包括但不限于:
- InputContext::cursorPosition
- InputContext::cursorRectangle
- InputContext::inputMethodHints
- InputContext::preeditText
- InputContext::selectedText
- InputContext::surroundingText
区域设置
虚拟键盘引擎会从layouts/ 中的特定区域设置布局目录中生成支持的区域设置列表。每个布局目录必须包含以下布局类型的定义或备用方案:拨号盘、数字、手写、主布局、数字和 符号。定义通过.qml 文件实现,备用方案则由扩展名为.fallback 的占位符文件定义。layouts/ 目录必须包含一个fallback/ 子目录,其中包含每种布局类型的定义。
每个布局目录可包含一种或多种布局类型的定义。如果特定语言环境的布局与备用语言环境的布局相同,您可以为该布局添加一个名为<layout type>.fallback 的占位符文件。这会指示虚拟键盘改用备用布局。
例如:您可以为芬兰语添加一个特定于该语言环境的布局,并在main.qml 中定义主布局类型。对于其他布局类型,则采用备用机制。您的layouts/ 目录结构将如下所示:
.
├── fallback
│ ├── dialpad.qml
│ ├── digits.qml
│ ├── handwriting.qml
│ ├── main.qml
│ ├── numbers.qml
│ └── symbols.qml
└── fi_FI
├── dialpad.fallback
├── digits.fallback
├── handwriting.fallback
├── main.qml
├── numbers.fallback
└── symbols.fallbacklayouts/fallback 目录必须始终包含一组完整的实现文件。
应用程序可以通过更改默认区域设置来指定初始布局。但是,这必须在应用程序初始化并加载输入法插件之前完成。如果未更改默认区域设置,则使用当前的系统区域设置。
键盘区域设置的匹配顺序如下:
layouts/<language>_<country>layouts/<language>_*layouts/fallback– 此处的默认布局为en_GB。
首先,根据完整区域设置名称进行匹配。如果没有完全匹配,则仅匹配区域设置的语言。最后,当也没有部分匹配时,将使用layouts/fallback 中的内容作为备用方案。
完成区域设置选择后,键盘会更新输入区域设置和输入方向以匹配当前布局。应用程序可通过QInputMethod 接口获取此信息。
在内部,当前输入区域设置也会同步更新至QVirtualKeyboardInputEngine 以及当前的输入法实例。
输入引擎
输入引擎对象由InputContext 拥有。与InputContext 类似,QVirtualKeyboardInputEngine 只有一个实例。输入引擎包含API函数,键盘通过这些函数将用户交互(如按键和释放事件)映射到输入法。
例如,虚拟键盘的按键事件是通过以下方法映射的:
上述方法旨在实现虚拟键盘的集成,因此方法名称中包含“virtual”一词。这也意味着这些方法不适用于映射物理按键操作。这是因为实际操作仅在按键释放时才会执行。
如果按键操作在按键释放事件发生前被中断,键盘将调用QVirtualKeyboardInputEngine::virtualKeyCancel 方法。
输入法
输入法是按键处理程序的具体实现。其主要功能是处理按键事件并维护用户输入的状态信息。它通过QVirtualKeyboardInputContext ,利用预编辑文本或键事件与文本编辑器进行交互。
输入法实例的创建方式因具体用例而异:
KeyboardLayout::inputMethod:键盘布局可以创建一个仅供该键盘布局使用的输入法实例。需要注意的是,当键盘布局发生变化时,该实例将被销毁。因此,此方法通常仅限于非常狭窄的使用场景。KeyboardLayout::createInputMethod(): 键盘布局可以动态创建一个输入法实例,该实例既可用于当前布局,也可用于shared layouts (例如符号布局)。这是创建专用输入法(如涉及复杂语言或手写输入的输入法)的首选方式。DefaultInputMethod:虚拟键盘会在启动时尝试创建此类输入法。除非键盘布局使用了自定义输入法,否则该实例将作为所有键盘布局的默认输入法。该实例在跨语言切换键盘布局时仍会保留,是创建和覆盖默认输入法的首选方式。
虚拟键盘插件
虚拟键盘的src/plugins目录中包含现有的虚拟键盘插件。这些插件是标准 QML 模块,由QtQuick 中的VirtualKeyboard.Plugins QML 模块隐式加载。
一个插件可以提供键盘布局和输入法(通常两者兼有)。虚拟键盘使用的输入法取决于当前使用的键盘布局。键盘布局可以通过KeyboardLayout.createInputMethod()函数提供自定义输入法的实例。否则,将使用虚拟键盘创建的默认输入法(DefaultInputMethod)。
添加键盘布局
插件可以通过在插件二进制文件的 Qt 资源中包含布局文件,为虚拟键盘添加键盘布局。
Qt Virtual Keyboard会从特定路径/qt-project.org/imports/QtQuick/VirtualKeyboard/Layouts/<LANGUAGE_COUNTRY> 中搜索(按语言划分的)键盘布局,因此插件中也必须使用完全相同的路径。 Qt 资源路径可能会重叠,这意味着插件可以覆盖 Virtual Keyboard 上的现有布局。
还可以通过使用QT_VIRTUALKEYBOARD_LAYOUT_PATH环境变量,将内置键盘布局直接从文件系统加载,从而覆盖它们。
添加输入法
该插件可以注册一种其他键盘布局默认使用的输入法(例如DefaultInputMethod ),或者一种在插件内部私有使用的输入法(同时提供自定义键盘布局,从而创建该输入法)。
该输入法必须实现QVirtualKeyboardAbstractInputMethod (C++)或InputMethod (QML)接口,并且必须由插件作为 QML 类型(QML_NAMED_ELEMENT )进行注册。
实现自定义输入法
实现输入法首先要确定使用 QML 还是 C++ 接口。本示例使用 QML 接口。相同的逻辑和接口也适用于 C++ 接口QVirtualKeyboardAbstractInputMethod 。在这种情况下,插件必须与VirtualKeyboard模块关联。
以下示例展示了输入法所需的最低功能:
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.VirtualKeyboard as VKB
// file: CustomInputMethod.qml
VKB.InputMethod {
function inputModes(locale) {
return [VKB.InputEngine.InputMode.Latin];
}
function setInputMode(locale, inputMode) {
return true
}
function setTextCase(textCase) {
return true
}
function reset() {
// TODO: reset the input method without modifying input context
}
function update() {
// TODO: commit current state and update the input method
}
function keyEvent(key, text, modifiers) {
var accept = false
// TODO: Handle key and set accept or fallback to default processing
return accept;
}
}在设置输入模式之前,输入引擎会调用InputMethod::inputModes() 方法。该方法返回给定区域设置中可用的输入模式列表。
输入法在InputMethod::setInputMode() 方法中通过区域设置和输入模式进行初始化。设置区域设置和输入模式后,输入法应已准备就绪,可供使用。
InputMethod::reset() 方法在需要重置输入法时被调用。重置操作仅应重置输入法的内部状态,而不应影响用户输入的文本。
InputMethod::update当输入上下文更新且输入状态可能出现不一致时,会调用该方法。输入法应提交当前文本。
按键事件在 `InputMethod::keyEvent()` 中处理。该方法处理单个按键事件,若事件已处理,则返回 `true `;否则,该按键将由默认输入法处理。
选择列表
选择列表是一项可选功能,可集成到输入法中。输入框架支持多种类型的列表,例如词汇候选列表。 在实现列表时,职责划分如下:输入法负责内容及相关操作(如点击行为);输入框架则负责维护列表模型并将其传递给用户界面。
分配选择列表
选择列表在输入法激活时进行分配。InputMethod::selectionLists() 方法返回所需选择列表类型的列表:
function selectionLists() {
return [SelectionListModel.Type.WordCandidateList];
}在上例中,输入法为其自身使用分配了词汇候选项列表。
更新选择列表
当输入法需要用户界面更新选择列表的内容时,它会发出InputMethod::selectionListChanged 信号。同样,如果输入法需要用户界面突出显示列表中的某个项目,它会发出InputMethod::selectionListActiveItemChanged 信号。
selectionListChanged(SelectionListModel.Type.WordCandidateList)
selectionListActiveItemChanged(SelectionListModel.Type.WordCandidateList, wordIndex)填充选择列表中的项目
列表项通过方法回调进行填充,这些回调将提供列表中的项数以及各个项的数据。
InputMethod::selectionListItemCount 回调用于请求指定类型列表中的项目数量。
function selectionListItemCount(type) {
if (type == SelectionListModel.Type.WordCandidateList) {
return wordList.length
}
return 0
}InputMethod::selectionListData 回调用于请求项目的数据。
function selectionListData(type, index, role) {
var result = null
if (type == SelectionListModel.Type.WordCandidateList) {
switch (role) {
case SelectionListModel.Role.Display:
result = wordList[index]
break
default:
break
}
}
return result
}role 参数用于指定要请求的某项数据。例如,SelectionListModel.Role.Display 请求显示文本数据。
响应用户操作
当用户在列表中选择一个项目时,输入方法会在InputMethod::selectionListItemSelected 方法回调中响应该事件。
function selectionListItemSelected(type, index) {
if (type == SelectionListModel.Type.WordCandidateList) {
inputContext.commit(wordlist[index])
update()
}
}集成手写识别
输入方法还可以使用来自触摸屏或其他输入设备的输入数据。
当输入开始时,虚拟键盘会调用输入方法函数 traceBegin ,该函数会返回一个新的Trace 对象,该对象将代表输入方法收集输入内容。同样地,当手指或触控笔抬起时,通过调用 traceEnd 来终止该事件。输入方法会处理收集到的数据,并使用InputContext 接口生成文本。
手写功能提供了预定义的键盘布局。不过,这些布局默认并未包含在内,手写插件应将其纳入自身的资源中。有关具体实现方法的示例,请参考MyScript或Cerence 提供的现有手写插件。
手写输入的数据模型
虚拟键盘会将手写数据收集到一种特殊的数据模型中:QVirtualKeyboardTrace 。每个笔迹(trace)代表从一次触摸(例如在屏幕上滑动)中采样得到的一组数据。QVirtualKeyboardTrace 的实例数量将与手写输入区域上的触摸次数相同。
根据定义,轨迹是一组从一次触摸中采样得到的数据。除了基本点数据外,它还可以包含其他类型的数据,例如每个点的时间。输入法可以在轨迹事件开始时定义所需的输入通道。
输入方法并不参与 trace 数据的实际采集。但是,输入方法对输入具有完全控制权,因为它可以接受或拒绝一个QVirtualKeyboardTrace (例如,当实例数量过多而无法处理时)。这还允许对可同时使用的指头数量进行精确控制。
输入方法可以根据需要收集任意数量的跟踪数据,并在必要时开始处理这些数据。 处理工作甚至可以在采样数据的同时并行进行,尽管由于潜在的性能问题,不建议这样做。推荐的做法是在距离上次输入经过适当延迟后,在后台线程中开始处理,以确保处理过程不会对用户界面产生负面影响。
输入法的轨迹 API
跟踪 API 由以下虚拟方法组成,输入法必须实现这些方法才能接收和处理跟踪输入数据。
通过实现这些方法,输入法可以接收并处理来自各种输入源(例如键盘布局或全屏)的数据。
patternRecognitionModes 方法返回输入法支持的图案识别模式列表。图案识别模式(例如 Handwriting )定义了输入法处理数据的方式。
当输入源检测到新的接触点并为新的 trace 对象调用 traceBegin 方法时,轨迹交互即被启动。如果输入方法接受该交互,它将创建一个新的 trace 对象并将其返回给调用方。从此时起,系统将持续收集轨迹数据,直至调用 traceEnd 方法。
当调用 traceEnd 方法时,输入方法可开始处理 trace 对象中包含的数据。处理完数据后,输入方法应销毁该对象。这也会清除屏幕上渲染的轨迹。
键盘布局
键盘布局位于src/layouts/builtin目录下。 布局目录下的每个子目录代表一种区域设置。区域设置目录的名称采用“language_country”的形式,其中 language 是小写的、由两个字母组成的 ISO 639 语言代码,country 是大写的、由两个或三个字母组成的 ISO 3166 国家代码。
布局类型
不同的输入模式会使用不同的键盘布局类型。用于常规文本输入的默认布局称为“main”布局。布局类型由布局文件名决定。因此,“main”布局文件名为“main.qml”。
支持的布局类型列表:
main用于常规文本输入的主布局symbols用于特殊字符等的符号布局(从主布局激活)numbers用于格式化数字的数字布局(通过Qt::ImhFormattedNumbersOnly 激活)digits仅数字布局(通过Qt::ImhDigitsOnly 激活)dialpad用于输入电话号码的拨号盘布局(通过Qt::ImhDialableCharactersOnly 激活)handwriting用于手写识别的手写布局(从主布局激活)
添加新的键盘布局
键盘布局元素必须基于KeyboardLayout QML 类型。该类型定义了布局的根项。根项具有以下可选属性,可在必要时进行设置:
property var inputMethod | 指定此布局的输入法。如果未定义输入法,则使用当前输入法。 |
property int inputMode | 指定此布局的输入模式。 |
property real keyWeight | 指定此键盘布局中所有按键使用的默认按键权重。按键权重是一个比例值,用于调整各个按键之间相对大小的比例关系。 |
通过使用KeyboardRow 类型向键盘布局添加新行。KeyboardRow 还可以为其子元素指定默认键权重。否则,键权重将从其父元素继承。
使用 Key 类型或其中一种专用键类型,可向键盘行中添加新键。以下是所有键类型的列表:
键盘布局中的退格键 | |
键盘布局中的“切换语言”键 | |
键盘布局的回车键 | |
键盘布局中的填充键 | |
键盘布局的“轻扫”键 | |
键盘布局中的手写模式键 | |
键盘布局的“隐藏键盘”键 | |
键盘布局的输入模式键 | |
键盘布局的常规字符键 | |
键盘布局的通用模式键 | |
键盘布局专用数字键 | |
键盘布局的 Shift 键 | |
键盘布局的空格键 | |
键盘布局的符号模式键 | |
用于采集触摸输入数据的专用键 |
例如,要添加一个常规按键,使其向输入法发送按键事件:
import QtQuick
import QtQuick.VirtualKeyboard
import QtQuick.VirtualKeyboard.Components
// file: en_GB/main.qml
KeyboardLayout {
keyWeight: 160
KeyboardRow {
Key {
key: Qt.Key_Q
text: "q"
}
}
}键位尺寸计算
键盘布局是可缩放的,这意味着布局中的任何项目都不能设置固定尺寸。相反,按键宽度是根据按键权重之间的相对关系计算得出的,而高度则是通过将键盘行间距平均分配来确定的。
在上例中,键的大小按以下顺序从父元素继承:
Key >KeyboardRow >KeyboardLayout
键重量的实际值为 160。为了便于说明,我们再添加一个指定自定义键重量的键:
import QtQuick
import QtQuick.VirtualKeyboard
import QtQuick.VirtualKeyboard.Components
// file: en_GB/main.qml
KeyboardLayout {
keyWeight: 160
KeyboardRow {
Key {
key: Qt.Key_Q
text: "q"
}
Key {
key: Qt.Key_W
text: "w"
keyWeight: 200
}
}
}现在,一行按键的总权重为160 + 200 = 360。当键盘布局被激活时,单个按键的宽度计算方式如下:
按键宽度(以像素为单位)= 按键权重 / 该行按键权重之和 * 行宽度(以像素为单位)
这意味着键盘可以缩放至任意大小,而按键的相对大小保持不变。
替代键
Key 对象可以指定 alternativeKeys 属性,当用户按住该键时,会弹出一个列出替代键的弹出窗口。alternativeKeys 可以指定一个字符串,也可以指定一个字符串列表。如果 alternativeKeys 是一个字符串,用户可以在该字符串中的字符之间进行选择。
样式与布局
键盘布局不能指定任何视觉元素。相反,布局通过键盘样式进行可视化呈现。另一方面,键盘样式不会影响键盘布局的大小。
包含多页按键的键盘布局
某些键盘布局(例如符号布局)可能包含的键数超过单个键盘布局所能呈现的数量。一种解决方案是使用 `KeyboardLayoutLoader` 将多个键盘布局嵌入到同一上下文中。
当将 `KeyboardLayoutLoader ` 用作键盘布局的根项时,实际的键盘布局会被封装在 `Component` 元素中。通过将活动组件的 id 赋值给 `sourceComponent` 属性,即可激活该键盘布局。
例如:
import QtQuick
import QtQuick.VirtualKeyboard
import QtQuick.VirtualKeyboard.Components
// file: en_GB/symbols.qml
KeyboardLayoutLoader {
property bool secondPage
onVisibleChanged: if (!visible) secondPage = false
sourceComponent: secondPage ? page2 : page1
Component {
id: page1
KeyboardLayout {
KeyboardRow {
Key {
displayText: "1/2"
functionKey: true
onClicked: secondPage = !secondPage
}
}
}
}
Component {
id: page2
KeyboardLayout {
KeyboardRow {
Key {
displayText: "2/2"
functionKey: true
onClicked: secondPage = !secondPage
}
}
}
}
}手写键盘布局
每个支持手写识别的语言都必须提供一个名为handwriting.qml 的特殊键盘布局。
此类键盘布局必须满足以下要求:
- 在键盘布局中包含一个 `TraceInputKey `
- 提供一个 HandwritingInputMethod 实例作为输入法。
手写布局还可以包含ChangeLanguageKey 。为此,必须使用customLayoutsOnly 属性,该属性将过滤掉不使用手写功能的语言。
主布局和手写布局都应包含一个用于启用和禁用手写输入模式的键。这可以通过在布局中添加HandwritingModeKey 来实现。
添加自定义布局
虚拟键盘布局系统既支持内置布局,也支持自定义布局。内置布局作为Qt 资源嵌入到插件二进制文件中。自定义布局位于文件系统中,因此无需重新编译虚拟键盘本身即可安装;或者,它们也可以位于资源文件中。
运行时布局的选择受QT_VIRTUALKEYBOARD_LAYOUT_PATH 环境变量的影响。
如果未设置该环境变量,或者其包含无效的目录,虚拟键盘将回退到默认的内置布局。
若在使用自定义布局时,需防止将内置布局编译到虚拟键盘插件中,请在configure 脚本中添加-no-vkb-layouts 选项。更多信息,请参阅“配置选项”。
键盘样式
虚拟键盘样式系统支持内置样式和自定义样式。内置样式作为 Qt 资源嵌入到插件二进制文件中,而自定义样式位于文件系统中,无需重新编译虚拟键盘本身即可安装。
运行时样式的选择受环境变量 QT_VIRTUALKEYBOARD_STYLE 的影响,该变量可设置为内置样式的名称(例如“retro”),或设置为安装在 Styles 目录中的任何自定义样式:
$$[QT_INSTALL_QML]/QtQuick/VirtualKeyboard/Styles如果未设置该环境变量,或者其包含无效的样式名称,虚拟键盘将回退到默认的内置样式。
添加自定义样式
创建新样式首先需要在基于 URL 的目录结构QtQuick/VirtualKeyboard/Styles/ 下的 QML 导入路径中,为该样式创建一个新子目录。有关QML 导入路径的信息,请参阅QML 导入路径。 目录名称不能包含空格或下划线以外的特殊字符。此外,目录名称不能与任何内置样式名称相同,目前内置样式包括“default”和“retro”。
创建新样式的良好起点是使用现有的内置样式作为模板并对其进行编辑。 您可以在虚拟键盘源代码目录 src/styles/builtin 中找到内置样式。将包含某个内置样式的目录复制到Styles目录中,并将其重命名为“test”。此时目录结构应如下所示:
test/default_style.qrc
test/style.qml
test/images
test/images/backspace.png
test/images/check.png
test/images/enter.png
test/images/globe.png
test/images/hidekeyboard.png
test/images/search.png
test/images/shift.pngQRC 配置文件在此情况下已无必要,可以安全地将其删除。
注意: style.qml 文件不应重命名,否则虚拟键盘将无法加载该样式。
接下来,使用您常用的编辑器打开 style.qml 文件,并将 resourcePrefix 属性设置为空字符串。由于资源与 style.qml 文件位于同一目录下,因此无需指定资源前缀。
此外,为了更直观地显示自定义样式确实已被加载并使用,请将键盘背景色设置为其他颜色:
keyboardBackground: Rectangle {
color: "gray"
}最后一步是使用自定义样式运行示例应用程序:
QT_VIRTUALKEYBOARD_STYLE=test virtualkeyboard在 QQuickWidget 中使用Qt Virtual Keyboard
在触摸设备上的QQuickWidget 中使用Qt Virtual Keyboard 时,必须通过QWidget::setAttribute()设置Qt::WA_AcceptTouchEvents 属性。若未设置该属性,来自触摸设备的事件将被转换为合成鼠标事件。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.