Qt Virtual Keyboard 概述

功能特点

Qt Virtual Keyboard 的主要功能包括

  • 可定制键盘布局和样式,并可动态切换。
  • 带单词选择的预测性文本输入。
  • 字符预览和替代字符视图。
  • 自动大写和空格插入
  • 可扩展至不同分辨率
  • 支持不同的字符集(拉丁文、简体中文/繁体中文、印地文、日文、阿拉伯文、希伯来文、韩文等)。
  • 支持大多数常用输入语言,并可轻松扩展语言支持。
  • 从左到右和从右到左输入。
  • 硬件按键支持双向和五向导航。
  • 支持手写输入,全屏手势输入。
  • 音频反馈
  • 跨平台功能。
  • 支持Qt QuickQt Widgets 应用程序

支持的语言

虚拟键盘支持以下语言:

要添加对其他语言的支持,请参阅添加新键盘布局

第三方插件

Qt Virtual Keyboard 支持以下供应商的第三方插件:

构建Qt Virtual Keyboard描述了如何将这些插件集成到Qt Virtual Keyboard 中。

基本概念

Qt Virtual Keyboard 项目是一个 Qt 输入上下文插件,它实现了 QPlatformInputContextPlugin 和 QPlatformInputContext 接口。这些接口允许该插件在 Qt 应用程序中用作平台输入上下文插件。

插件本身提供了一个输入框架,支持多种输入方法以及虚拟键盘的 QML UI。输入框架可通过插件接口进行扩展,允许在运行时加载第三方输入法和键盘布局。

输入框架提供以下主要接口:

  • QVirtualKeyboardInputContext为虚拟键盘和其他输入组件提供上下文信息。作为底层文本输入组件的接口。
  • QVirtualKeyboardInputEngine输入法:提供集成用户输入事件(按键等)的 API,并充当输入法的主机。
  • QVirtualKeyboardAbstractInputMethod输入法:基于 C++ 的输入法的基本类型。输入法通常处理按键事件,但也可处理鼠标和触摸输入事件。
  • InputMethod:基于 QML 的输入法的基本类型。输入法通常处理按键事件,但也可处理鼠标和触摸输入事件。

输入上下文

InputContext 是 QML 托管的单例。应用程序不应直接与输入上下文交互。

上下文信息

输入上下文可访问源于应用程序的上下文信息。这些信息包括但不限于

地域

虚拟键盘引擎会从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.fallback

layouts/fallback 目录必须始终包含一组完整的实现文件。

应用程序可以通过更改默认本地语言来指定初始布局。不过,这必须在应用程序初始化和加载输入法插件之前完成。如果没有更改默认本地语言,则使用当前的系统本地语言。

匹配键盘区域设置的顺序如下:

  • layouts/<language>_<country>
  • layouts/<language>_*
  • layouts/fallback - 这里的默认布局是en_GB

首先,根据完整的本地语言名称匹配本地语言。如果不完全匹配,则只匹配本地语言。最后,如果也没有部分匹配,则使用layouts/fallback 的内容作为后备。

完成本地语言选择后,键盘会更新输入本地语言和输入方向,以匹配当前布局。应用程序可通过QInputMethod 接口接收此信息。

在内部,当前输入法域也会更新为QVirtualKeyboardInputEngine 和当前输入法实例。

输入引擎

输入引擎对象归InputContext 所有。与InputContext 一样,QVirtualKeyboardInputEngine 只有一个实例。输入引擎包含 API 函数,键盘使用这些函数将用户交互(如按键和释放事件)映射到输入法。

例如,虚拟键盘按键事件通过以下方法映射:

上述方法用于集成虚拟键盘,因此在方法名称中使用了 "虚拟 "一词。这也意味着这些方法不适合映射物理按键。这是因为实际操作只有在按键释放时才会执行。

如果在按键释放事件之前按键中断,键盘会调用QVirtualKeyboardInputEngine::virtualKeyCancel 方法。

输入法

输入方法是按键处理程序的具体实现。它的主要功能是处理按键事件和维护用户输入的状态信息。它通过QVirtualKeyboardInputContext 预编辑文本或按键事件与文本编辑器进行交互。

输入法实例可以根据使用情况以不同方式创建:

  • KeyboardLayout::inputMethod键盘布局:键盘布局可以为该键盘布局创建一个输入法实例。需要注意的是,当键盘布局发生变化时,该实例将被销毁。因此,这种方法通常仅限于非常狭窄的使用情况。
  • KeyboardLayout::createInputMethod()输入法:键盘布局可以动态创建一个输入法,该输入法既可用于该布局,也可用于shared layouts (如符号布局)。这是创建专用输入法的首选方式,例如涉及复杂语言或手写输入法的输入法。
  • DefaultInputMethod注意:虚拟键盘会在启动时尝试创建此类输入法。除非键盘布局使用自定义输入法,否则该实例将在所有键盘布局中用作默认输入法。此实例的寿命比不同语言的键盘布局变化要长,是创建和覆盖默认输入法的首选方式。

虚拟键盘插件

虚拟键盘的src/plugins目录包含虚拟键盘的现有插件。这些插件是由QtQuick.VirtualKeyboard.Plugins QML 模块隐式加载的标准 QML 模块。

插件可提供键盘布局和输入法(通常两种都有)。虚拟键盘使用的输入法取决于使用的键盘布局。键盘布局可通过KeyboardLayout.createInputMethod() 函数提供自定义输入法实例。否则,将使用虚拟键盘创建的默认输入法 (DefaultInputMethod)。

添加键盘布局

插件可通过在插件二进制文件的 Qt 资源中包含布局文件,为虚拟键盘添加键盘布局。

虚拟键盘会从特定路径/qt-project.org/imports/QtQuick/VirtualKeyboard/Layouts/<language_COUNTRY>中搜索键盘布局(每种语言)因此在插件中也必须使用该路径。Qt 资源路径可以重叠,这意味着插件可以覆盖虚拟键盘上的现有布局。

通过使用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

// file: CustomInputMethod.qml

InputMethod {
    function inputModes(locale) {
        return [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 界面生成文本。

手写输入法有预定义的键盘布局。不过,默认情况下并不包含这些布局,手写插件应在自己的资源中包含这些布局。有关如何做到这一点的示例,请参阅MyScriptCerence 现有的手写插件。

手写输入的数据模型

虚拟键盘将手写数据收集到一个特殊的数据模型QVirtualKeyboardTrace 中。每个轨迹代表从一次触摸(如轻扫屏幕)中采样的数据集合。手写输入区域上有多少次触摸,就会有多少个QVirtualKeyboardTrace 实例。

顾名思义,轨迹就是从一次触摸中采样的一组数据。除了基本的点数据外,它还可以包括其他类型的数据,例如每个点的时间。输入法可以在跟踪事件开始时定义所需的输入通道。

输入法不参与轨迹数据的实际采集。不过,输入方法可以完全控制输入,因为它可以接受或拒绝QVirtualKeyboardTrace (例如,如果有太多实例需要处理)。这样还可以精确控制可以同时使用多少个手指。

输入法可以收集尽可能多的轨迹,并在必要时开始处理这些轨迹。甚至可以在对数据进行采样的同时并行处理,但不建议这样做,因为会有潜在的性能问题。建议的方法是在最后一次输入后适当延迟一段时间,然后在后台线程中开始处理,这样处理过程就不会对用户界面产生负面影响。

输入法的跟踪 API

跟踪 API 由以下虚拟方法组成,输入方法必须实现这些方法才能接收和处理跟踪输入数据。

通过实施这些方法,输入法可以接收和处理来自各种输入源(如键盘布局或全屏)的数据。

patternRecognitionModes 方法返回输入法支持的模式识别模式列表。模式识别模式(如 Handwriting )定义了输入法处理数据的方法。

当输入源检测到新的接触点并调用 traceBegin 方法创建新的跟踪对象时,跟踪交互就开始了。如果输入法接受了交互,它就会创建一个新的跟踪对象并将其返回给调用者。从这时起,跟踪数据将一直收集到 traceEnd 方法被调用为止。

调用 traceEnd 方法时,输入法可以开始处理跟踪对象中的数据。处理完数据后,输入方法应销毁该对象。这也会删除渲染到屏幕上的跟踪。

键盘布局

键盘布局位于src/layouts/builtin目录中。布局目录下的每个子目录都代表一个 locale。locale目录是一个 "language_country "形式的字符串,其中language是小写、两个字母的 ISO 639 语言代码,country是大写、两个或三个字母的 ISO 3166 国家代码。

布局类型

不同的输入模式使用不同的键盘布局类型。用于常规文本输入的默认布局称为 "主 "布局。布局类型由布局文件名决定。因此,"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 也可以为其子元素指定默认键重。否则,键重将从父元素继承。

使用按键类型或其中一种专用按键类型可将新按键添加到键盘行中。以下是所有按键类型的列表:

BackspaceKey

用于键盘布局的退格键

ChangeLanguageKey

更改语言键,用于键盘布局

EnterKey

回车键,用于键盘布局

FillerKey

填充键,用于键盘布局

FlickKey

轻移键,用于键盘布局

HandwritingModeKey

手写模式键,用于键盘布局

HideKeyboardKey

隐藏键盘键,用于键盘布局

InputModeKey

输入模式键,用于键盘布局

Key

常规字符键,用于键盘布局

ModeKey

通用模式键,用于键盘布局

NumberKey

专用数字键,用于键盘布局

ShiftKey

键盘布局中的 Shift 键

SpaceKey

空格键,用于键盘布局

SymbolModeKey

用于键盘布局的符号模式键

TraceInputKey

用于收集触摸输入数据的专用键

例如,添加一个向输入法发送按键事件的常规按键:

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"
        }
    }
}

按键大小计算

键盘布局是可伸缩的,这意味着不能为布局中的任何项目设置任何固定尺寸。相反,按键宽度是根据按键之间的重量关系计算得出的,而高度则是通过在键盘各行之间平均分配空间计算得出的。

在上述示例中,按键尺寸是按以下顺序从父元素继承的:

键 >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。激活键盘布局后,单个按键的宽度计算如下:

按键宽度(像素) = 按键权重 / SUM(一行中的按键权重) * 行宽(像素

这意味着键盘可以缩放至任何尺寸,而相对按键的尺寸保持不变。

替代按键

按键可以指定替代按键属性,当用户按住按键时,弹出窗口会列出替代按键。替代键可以指定一个字符串,也可以指定一个字符串列表。如果 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 的影响,该变量可设置为内置样式(如 "复古")或安装到样式目录中的任何自定义样式的名称:

$$[QT_INSTALL_QML]/QtQuick/VirtualKeyboard/Styles

如果环境变量未设置,或包含无效的样式名称,虚拟键盘将返回默认内置样式。

添加自定义样式

创建新样式的第一步是在基于 URL 的目录结构QtQuick/VirtualKeyboard/Styles/ 下的 QML 导入路径中为样式创建一个新子目录。有关 QML导入路径的信息,请参阅 QML 导入路径。目录名不能包含空格或下划线以外的特殊字符。此外,目录名称不能与内置样式相同,目前内置样式包括 "默认 "和 "复古"。

创建新样式的一个良好开端是使用现有的内置样式作为模板并对其进行编辑。您可以从虚拟键盘源代码目录 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.png

QRC 配置文件在本例中并无必要,可以放心删除。

注意: style.qml 文件不应重命名,否则虚拟键盘将无法加载该样式。

接下来,用喜欢的编辑器打开 style.qml,将 resourcePrefix 属性设置为空字符串。不需要资源前缀,因为资源与 style.qml 文件包含在同一目录中。

此外,为了更明显地显示自定义样式已被加载和使用,请将键盘背景设置为不同的颜色:

keyboardBackground: Rectangle {
    color: "gray"
}

最后一步是使用自定义样式运行示例程序:

QT_VIRTUALKEYBOARD_STYLE=test virtualkeyboard

使用Qt Virtual Keyboard 和 QQuickWidget

在触摸设备上的QQuickWidget 中使用Qt Virtual Keyboard 时,必须通过QWidget::setAttribute() 设置Qt::WA_AcceptTouchEvents 属性。如果不设置该属性,来自触摸设备的事件将被转换为合成鼠标事件。

© 2025 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.