尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
QML自制数字软键盘:向TextInput/TextField键入数字并实现左移右移删除
1. 嵌入式触屏里为什么还要自己写数字软键盘在嵌入式触屏设备上做输入很多人第一反应是调用系统输入法。但真到项目里你会发现系统输入法要么体积太大要么在 Qt for Device Creation 的镜像里根本没带要么弹出来的全键盘把半个屏幕都盖住了用户点一个数字要在一堆字母里找。尤其是电表、工控面板、门禁机、手持终端这类场景输入内容 90% 就是纯数字这时候一个自制数字软键盘反而更省事、更可控。QML 里做数字软键盘核心其实就三件事把按键布局画出来、把按键事件接到 TextInput 或 TextField 上、把光标位置 cursorPosition 管好。前两件是体力活第三件才是真正容易踩坑的地方。TextInput 和 TextField 都继承自 TextInput 的底层实现光标位置用cursorPosition表示它指的是光标停在第几个字符之后从 0 开始计数。比如输入框里是 123光标在最右边时 cursorPosition 等于 3在最左边时等于 0。围绕这个属性QML 提供了两个关键方法insert(position, text)在指定位置插入文本remove(start, end)删除从 start 到 end 之间的字符。把数字键、左移、右移、删除四个动作分别映射到这几个 API 上一个能用的数字软键盘就成型了。这篇就按可复制的顺序把组件代码、cursorPosition 联动配置、在 TextInput/TextField 上的验证步骤以及常见报错排查一次讲清楚适合正在做嵌入式触屏输入、又不想被系统输入法绑架的开发者直接落地。2. 动手前先把 TaoToken 配好方便边写边问写 QML 的过程中cursorPosition 的边界处理、insert/remove 的越界行为、TextField 和 TextInput 的差异这些细节翻文档能查到但翻起来慢。我习惯在写代码的同时挂一个能随时问的模型通道遇到不确定的 API 行为直接问比来回翻 Qt Help 快很多。这里用的是 TaoToken它把常见大模型的调用统一到一个入口配置一次就能在编辑器插件或命令行里用。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 这个地址后面不加任何参数。它的作用简单说就是你拿到一个 Key把 Base URL 指向它就能在支持自定义端点的工具里调用模型用来查 QML 语法、生成按键布局模板、解释报错都行。适合谁用就是像我这样一边写嵌入式 QML、一边需要快速确认 API 细节的人。你不需要在多个平台之间来回切一个 Key 走通。下面先把 Key 拿到再回到 QML 本身。拿 Key 的路径是进控制台在 API Keys 页面创建一个新 Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 页面是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。创建时给它起个能认出来的名字比如 qml-dev方便后面区分。Key 只在创建时完整显示一次复制下来存好。如果你用的是 Claude Code 这类命令行编码工具TaoToken 也提供了对应的接入方式文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Claude Code 的接入页是 https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。想先试试模型对话效果可以直接开 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里的对话页把一段 QML 报错贴进去问验证通道是否通。这一步不是必须的但配好之后后面写按键逻辑时遇到 cursorPosition 越界、insert 位置算错这类问题能立刻问、立刻改省掉大量翻文档的时间。Key 拿到手我们就进入正题。3. 可复制的 QML 数字软键盘组件与 cursorPosition 配置这一节是全文的核心直接给能跑的代码。整体思路是做一个独立的NumberKeyboard.qml组件对外暴露一个target属性指向要输入的 TextInput 或 TextField内部所有按键都通过操作target.cursorPosition和调用target.insert/remove来完成输入。这样键盘和输入框解耦哪个输入框获得焦点就把 target 指过去。先看键盘组件的完整代码。布局用 GridLayout 排 0-9 和功能键功能键包括左移、右移、删除、清空。// NumberKeyboard.qml import QtQuick 2.15 import QtQuick.Layouts 1.15 Rectangle { id: keyboard width: 300 height: 400 color: #f0f0f0 // 外部把要输入的 TextInput / TextField 赋给这个属性 property var target: null // 安全插入先判断 target 是否有效 function safeInsert(text) { if (!target) return var pos target.cursorPosition target.insert(pos, text) // insert 之后光标不会自动后移需要手动 1 target.cursorPosition pos 1 } // 安全删除删除光标前一个字符 function safeBackspace() { if (!target) return var pos target.cursorPosition if (pos 0) return // 已经在最左边不删 target.remove(pos - 1, pos) target.cursorPosition pos - 1 } // 左移不能小于 0 function moveLeft() { if (!target) return if (target.cursorPosition 0) target.cursorPosition - 1 } // 右移不能超过文本长度 function moveRight() { if (!target) return if (target.cursorPosition target.length) target.cursorPosition 1 } GridLayout { anchors.fill: parent anchors.margins: 8 columns: 3 rowSpacing: 8 columnSpacing: 8 Repeater { model: [1,2,3,4,5,6,7,8,9] delegate: KeyButton { text: modelData onClicked: keyboard.safeInsert(modelData) } } KeyButton { text: ←; onClicked: keyboard.moveLeft() } KeyButton { text: 0; onClicked: keyboard.safeInsert(0) } KeyButton { text: →; onClicked: keyboard.moveRight() } KeyButton { text: Del; onClicked: keyboard.safeBackspace() } KeyButton { text: 清空; onClicked: { if (keyboard.target) { keyboard.target.text ; keyboard.target.cursorPosition 0 } } } } }上面用到一个KeyButton子组件单独抽出来复用// KeyButton.qml import QtQuick 2.15 import QtQuick.Layouts 1.15 Rectangle { id: btn Layout.fillWidth: true Layout.fillHeight: true radius: 6 color: mouseArea.pressed ? #c0c0c0 : #ffffff border.color: #cccccc property alias text: label.text signal clicked() Text { id: label anchors.centerIn: parent font.pixelSize: 24 } MouseArea { id: mouseArea anchors.fill: parent onClicked: btn.clicked() } }关键点在于safeInsert里那句target.cursorPosition pos 1。很多人第一次写会漏掉它结果就是连续点数字时第二个数字插到了第一个数字前面因为 insert 不会自动移动光标。这是 cursorPosition 操作里最常见的坑后面排障章节还会展开。再看主界面怎么把键盘和输入框接起来。假设你有一个 TextField 用来输入数量import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow { visible: true width: 480 height: 800 Column { anchors.fill: parent spacing: 20 padding: 20 TextField { id: qtyInput width: parent.width - 40 placeholderText: 请输入数量 inputMethodHints: Qt.ImhDigitsOnly // 只允许数字 onActiveFocusChanged: { if (activeFocus) numberKeyboard.target qtyInput } } NumberKeyboard { id: numberKeyboard width: parent.width - 40 height: 400 } } }这里用onActiveFocusChanged把当前获得焦点的输入框赋给键盘的 target。如果你有多个输入框每个都加同样的逻辑谁获得焦点谁就是输入目标不用手动切换。inputMethodHints: Qt.ImhDigitsOnly是给系统输入法看的提示自制键盘场景下它不影响我们的逻辑但保留着没坏处。如果你用的是 TextInput 而不是 TextField代码几乎不用改因为两者在 cursorPosition、insert、remove 上的行为一致。区别在于 TextField 自带背景和样式TextInput 是纯文本输入区域需要自己画背景。把上面 TextField 换成 TextInput 时记得给它加个 Rectangle 背景否则在触屏上看不清输入区域边界。到这一步组件和联动配置就齐了。把这三个文件放进工程主界面引用 NumberKeyboard就能看到键盘并点击输入。4. 验证请求在 TextInput/TextField 上跑通键入、左移右移与删除代码写完不能只看得实际点一遍确认行为符合预期。这一节给一套验证步骤照着做能覆盖所有边界情况。第一步先验证最基本的数字键入。运行程序点击输入框让它获得焦点然后依次点键盘上的 1、2、3。预期结果是输入框显示 123光标停在 3 后面。如果你看到的是 321说明safeInsert里漏了cursorPosition pos 1这行回去补上。第二步验证中间插入。输入框里现在是 123光标默认在最右。先点两次左移键光标移到 1 和 2 之间cursorPosition 等于 1。此时点数字 9预期结果是 1923光标停在 9 后面。这一步专门测 insert 的位置参数是否正确因为 insert 用的是当前 cursorPosition如果光标没移对插入位置就会错。第三步验证删除。在 1923 状态下光标在 9 后面cursorPosition 等于 2。点一次 Del预期结果是 1239 被删掉光标回到 1 后面。再连点 Del 直到删空确认删到 cursorPosition 为 0 时不再继续删不会报错。这一步测的是safeBackspace里的if (pos 0) return边界判断。第四步验证左移右移的边界。输入框清空后光标在 0。点左移光标应该不动不会变成 -1。输入 12 后光标在 2点右移光标应该不动不会超过 length。这两个边界如果没判断cursorPosition 会越界轻则光标乱跳重则 insert/remove 抛异常。第五步验证多输入框切换。放两个 TextField分别获得焦点确认键盘的 target 跟着焦点走输入落到正确的框里。这一步测的是onActiveFocusChanged的绑定逻辑。跑完这五步基本功能就确认没问题了。如果你想在验证过程中让模型帮你解释某一步的预期行为可以把当前代码和现象贴到模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里问比对着文档猜快。验证通过后再考虑把它接到真实业务输入框上。5. 常见报错排查cursorPosition 越界、insert 位置错、焦点丢失这一节把实际写的时候最容易撞上的几个问题列出来对照现象找原因。现象一连续点数字顺序反了。输入框显示 321 而不是 123。原因是 insert 之后没有更新 cursorPosition。insert(position, text) 只负责插入不负责移动光标光标还停在原位下一个数字又插到前面去了。修复就是在 safeInsert 里插入后手动target.cursorPosition pos 1。现象二报错Cannot assign to non-existent property cursorPosition或类似。说明 target 指向的对象不是 TextInput/TextField或者 target 是 null。检查property var target有没有被正确赋值以及赋值时输入框是否已经创建。如果 target 为 null所有操作都要先判断代码里的if (!target) return就是干这个的。现象三点删除没反应或者删错位置。检查 remove 的参数。remove(start, end)删除的是 [start, end) 区间删除光标前一个字符应该是remove(pos - 1, pos)不是remove(pos, pos - 1)。参数顺序反了会删不掉或者删错。另外确认 pos 大于 0否则会删到负数索引。现象四光标左移右移越界程序卡住或光标消失。cursorPosition 的合法范围是 0 到 length。左移时如果已经是 0 还减会变成 -1右移时如果已经等于 length 还加会超过文本长度。两个方向都要加边界判断代码里的 moveLeft/moveRight 已经处理了。现象五切换输入框后输入还是落到旧框里。焦点绑定没生效。确认每个输入框都写了onActiveFocusChanged并且在获得焦点时把自身赋给 keyboard.target。如果用的是 TextField 的focus属性手动控制注意activeFocus和focus的区别触屏场景下用 activeFocus 更准。现象六local proxy failed或401这类报错。这通常不是 QML 本身的问题而是你在用编辑器插件或命令行工具调模型时通道没配对。401 一般是 Key 无效或没带上检查 API Keys 页面里 Key 是否还在、有没有复制完整。local proxy failed多半是本地代理配置和实际端点不一致确认 Base URL 指向的是 https://taotoken.net/api 并且没有多余路径。这类接入问题可以在接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里对照排查。现象七reading choices报错。这是解析模型返回时字段对不上通常出现在自己写请求代码的场景。检查请求体里的 model 字段是否填了有效的模型 ID以及返回结构是否按标准格式解析。如果你用的是现成工具确认工具版本和端点兼容。把上面这些对照一遍大部分 cursorPosition 相关的问题都能定位。排查完还搞不定的把完整报错和最小复现代码贴到模型对话里问通常一两轮就能找到原因。6. 把键盘接到真实业务里下一步怎么走组件跑通、验证通过之后真正落地还有几件事值得做。一是把键盘做成可复用的单例或者全局组件避免每个页面都复制一份二是根据业务限制输入长度比如数量最多 6 位在 safeInsert 里加长度判断三是给按键加触屏反馈按下变色、加音效提升手感四是处理键盘遮挡问题输入框在屏幕下半部分时键盘弹出要能自动上移或者滚动到可见区域。如果你后面要做更复杂的输入比如带小数点的金额、带负号的数值思路是一样的只是在 safeInsert 里对输入字符做校验或者在键盘上多加几个功能键。cursorPosition 这套 API 足够支撑这些扩展。长期在嵌入式 QML 和编码工具之间来回切换的话可以考虑用 Coding Plan 把模型调用固定下来地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 适合需要持续写代码、频繁问 API 细节的场景。如果只是偶尔查一下用模型对话页就够了。最后留一个我踩过的坑TextField 在获得焦点时系统可能会尝试弹出原生输入法在嵌入式设备上如果没装输入法这个动作可能引起焦点异常。稳妥的做法是在输入框上设置inputMethodHints并配合Qt.ImhNoPredictiveText或者在平台层面禁用原生输入法让自制键盘完全接管。这个细节在真机上才暴露模拟器里往往看不出来早点在目标设备上验证能省不少返工。
RELATED

相关推荐

charting-library-master-v28.3 源码解析:从本地运行到高性能渲染实践

charting-library-master-v28.3 源码解析:从本地运行到高性能渲染实践

简介:charting-library-master-v28.3 是 TradingView 高级图表库 v28.3.7 的完整源码包,面向金融交易领域的开发者、量化分析师及具备一定编程基础的技术人员,用于在自有平台中集成专业级行情图表与可视化分析能力。资源共包含 1212 个文件&a…

📅 2026/10/17 18:35:16
Makefile变量机制深度解析:$@ $^ $< override origin原理与工程实践

Makefile变量机制深度解析:$@ $^ $< override origin原理与工程实践

1. 为什么你写的 Makefile 总是“改一行,全重编”?——从变量失控说起我带过不少刚接触构建系统的开发者,他们写完第一个 Makefile 后常会兴奋地跑make,结果发现:明明只改了一个.c文件,整个项目却从头开始编…

📅 2026/10/17 18:35:16
【WorkBuddy专栏58】腾讯AI智能体「赛马」全景:7款产品对比与TaoToken统一接入实践

【WorkBuddy专栏58】腾讯AI智能体「赛马」全景:7款产品对比与TaoToken统一接入实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/17 18:35:16
MORE NEWS

更多资讯

📰

CentOS手动部署Tomcat完整教程:JDK配置、systemd托管与SELinux加固

1. 项目概述:为什么在 CentOS 上装 Tomcat 还得“完整版”?CentOS 安装 tomcat 环境安装及配置过程完整版——这个标题看着平平无奇,但背后藏着太多新手踩坑、老手翻车的真实现场。我带过不少刚从学校出来的开发实习生,也帮某高校…

📰

化工行业数字化解决方案:从DCS数据采集到软测量与报警管理落地实践

简介:这份PDF文档聚焦化工行业数字化转型,面向化工企业信息化负责人、ERP实施顾问及流程制造领域从业者,系统梳理了行业在销售接单报价、生产计划反馈、大宗物料管理、设备维护与成本核算等环节的关键痛点,并给出以ERP为核心、融合…

📰

自动驾驶决策规划全解析:从行为决策到运动规划的工程实践

简介:自动驾驶决策规划入门讲解资料,面向对智能车辆技术感兴趣的学生、工程师及产品经理,旨在帮助读者理解预测、决策与规划在自动驾驶整体架构中的核心作用。压缩包共1个PDF文件,大小7.3MB,内容以幻灯片形式呈现&…

📰

Interval Bound Propagation(IBP):神经网络鲁棒性验证的确定性边界工具

1. 这不是“普通”区间传播:IBP为何在鲁棒性验证中突然被反复提起最近在几个不同场景里,我都撞见了“Interval Bound Propagation”这个短语——不是出现在某篇冷门论文的附录里,而是扎扎实实跑在工业级模型验证流水线里。某高校实验室做自动…

📰

人工智能在护理领域的应用落地:风险预测与文书自动化实践

简介:围绕“人工智能在护理领域的应用进展”的PPT演示文稿,面向护理专业学生、临床护士、护理管理者及医疗信息化研究者,旨在帮助读者系统了解AI应对人口老龄化、疾病谱变化与医疗资源紧张等护理挑战的路径。内容以研究背景、应用现状、研究方…

📰

回溯去重全排列详解:Permutations II 剪枝模板与实战

这道题我第一次遇到的时候,心里想的是:“全排列 I 都写熟了,加个去重能有多难?”结果真上手才发现,难的不是写代码,而是搞清楚那两行剪枝条件背后的状态关系。Permutations II 的核心就一句话:输…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬