简介:这份 Qt 记事本工程面向具备 C++ 基础、希望学习 Qt6 桌面与嵌入式 GUI 开发的读者,也可作为课程设计或小型项目参考。项目以跨平台记事本为例,涉及主窗口交互、文本编辑、文件读写与多编码兼容、界面布局和资源打包,并兼顾嵌入式环境下的内存管理与性能优化思路。压缩包共 516 个文件,以 475 个 idx 索引文件为主,搭配 cpp/h 源码、ui/qrc 界面资源、pro/makefile 构建配置及 dll/a 库文件,整体约 4.29MB,工程结构相对完整。已有 778 人学习。读者可据此梳理 Qt6 工程的模块划分与信号槽协作方式,复现可运行的记事本界面,理解项目配置、资源编译和跨平台构建流程,并参考编码处理与嵌入式资源取舍,适合需要完整案例对照练习的开发者。
1. 一块触摸屏和一份 Qt 嵌入式记事本源码:先把使用边界说清
一块带触摸屏的 ARM 板,串口能通、屏幕能亮,但系统里没有一个趁手的便签工具——这类场景最适合拿一份 Qt 嵌入式记事本源码包来练手。它不是一个桌面级笔记软件,而是用 Qt Widgets 做的最小闭环:左侧笔记列表、右侧纯文本编辑区、本地落盘、触摸软键盘,能在 linuxfb 或 eglfs 下直接起窗口。能解决的是设备点检记录、调试便签、离线工单这类“板子上就地记一笔”的需求,不适合多用户同步和富文本排版。适合正在学 Qt 嵌入式交叉编译、需要给 HMI 加一个可运行模块的从业者,也适合刚接触 Qt 的同学拿它把信号槽、文件存储和输入法串一遍。源码包里的工程文件、部署脚本和资源目录,正好对应后面几章的复现路径。
2. 工程拆解:QWidget、存储层和构建链怎么分工
2.1 界面选 QWidget 还是 QML:先看内存和输入法
Qt 嵌入式记事本的第一道选型不是“哪个界面好看”,而是目标板还剩多少内存、有没有 GPU、触摸输入法准备怎么做。QWidget 走的是传统 C++ 控件树,依赖 qtbase 里的 Widgets 模块,绘制后端可以直接落到 framebuffer;QML 走场景图,动画和触控交互更顺,但在没有 GPU 的板子上常用软件后端,CPU 占用和启动时间都会往上走。记事本这种以文本编辑、列表切换为主的工具,交互复杂度不高,用 QWidget 更容易把内存和启动时间压在可控范围内。
我一般按下面这张表做初筛,实际项目再把数字换成板子上的实测值:
| 维度 | QWidget 方案 | QML 方案 | 记事本场景建议 |
|---|---|---|---|
| 运行时依赖 | qtbase 的 Widgets、平台插件 | Qt Quick、场景图、QML 运行时 | 低配板优先 QWidget |
| 内存占用 | 相对低,控件按需创建 | 场景图和动画缓存更吃内存 | 64MB 级板子先跑 Widgets |
| 输入法接入 | 可用 QInputMethod 或自绘键盘 | Qt Virtual Keyboard 集成更顺 | 中文输入要单独评估 |
| 触摸与动画 | 需要自己写按压态 | 原生手势和动画 | 记事本动画需求弱 |
| 维护成本 | C++ 信号槽,逻辑集中 | QML 与 C++ 分层,边界要设计 | 小团队选 Widgets 更直接 |
选 QWidget 不代表界面一定“土”。小屏记事本可以把左侧列表压到 160 到 200 像素,右侧编辑区用无边框 QTextEdit,再配一套深色 QSS,触摸目标至少留 40 像素见方。真正影响体验的是字体大小、行高和软键盘遮挡,不是控件框架本身。反过来说,如果源码包里已经带了 QML 界面,也不要硬改成 Widgets;先看它有没有依赖 OpenGL、有没有把 Qt Quick 模块裁掉,再决定是沿用还是重写。
输入法是选型里最容易低估的一块。Widgets 方案下,英文和数字键盘可以自己画一个 QDialog,点按钮往当前焦点控件插字符;中文输入则要接输入法引擎,常见做法是部署 Qt Virtual Keyboard 的拼音插件,或者调用系统里已有的输入法框架。前者资源占用不低,后者要看目标板根文件系统里有没有现成组件。我的习惯是先用自绘键盘把焦点、触摸和插入逻辑跑通,再评估中文输入方案,不要一上来就把完整拼音引擎塞进 64MB 的板子。
2.2 笔记落盘:纯文本、JSON、SQLite 三种方案怎么选
记事本的数据层看起来简单,真正翻车往往在“写一半掉电”和“笔记多了列表卡”。纯文本方案是一条笔记一个.txt,标题取首行,列表靠扫描目录;优点是能用文件管理器直接看,缺点是重命名、排序、全文检索都要自己补,笔记上千条后目录扫描会拖慢首屏。JSON 方案是单文件全量读写,几十条笔记很舒服,但每次保存都要重写整个文件,掉电时容易把整个文件写坏,除非配合临时文件加原子替换。SQLite 方案带事务和索引,列表分页、按更新时间排序、增量更新都更稳,代价是要把sqldrivers/libqsqlite.so一起部署到板子上。
这份 Qt 嵌入式记事本源码包如果定位是“可落地的最小闭环”,我建议用 SQLite 做主干,导出功能再走纯文本。表结构不用复杂,下面这一版够用:
CREATE TABLE IF NOT EXISTS notes ( id TEXT PRIMARY KEY, -- 用 QUuid 生成,避免多端合并时主键冲突 title TEXT NOT NULL, -- 从正文首行截断,列表只显示这一列 body TEXT NOT NULL, -- 纯文本正文,不存 HTML updated_at INTEGER NOT NULL -- Unix 秒级时间戳,排序和增量同步都用它 ); CREATE INDEX IF NOT EXISTS idx_notes_updated ON notes(updated_at DESC);id用QUuid::createUuid().toString(QUuid::WithoutBraces)生成,避免用自增整数在导入导出时撞主键。title不要单独让用户填,保存时取正文第一行,超过 24 个字符就截断并补省略号,小屏列表才不会撑破。updated_at用QDateTime::currentSecsSinceEpoch(),比存格式化字符串更省空间,排序也不用做字符串比较。索引按updated_at DESC建,首屏只查最近 50 条,别一上来就SELECT *全量拉。
写入时用事务包住“更新笔记 + 更新时间戳”,不要每条 SQL 单独提交。SQLite 在嵌入式存储上最怕频繁 fsync,批量写入能明显减少卡顿。如果源码包里用的是 QSqlDatabase,记得在打开数据库后执行PRAGMA journal_mode=WAL;,但 WAL 会在同目录多出-wal和-shm文件,只读根文件系统上放不下,必须把数据库路径指到可写分区。这个点后面避坑章节还会展开。
2.3 目录与构建:qmake/CMake 两条路和部署文件清单
拿到源码包先别急着编译,先看目录边界。一个能维护的 Qt 嵌入式记事本,通常会把界面、存储、部署脚本分开,避免所有代码堆在mainwindow.cpp里。下面是我拆这类工程时常见的目录形态,源码包不一定完全一样,但层次可以对得上:
notepad/ ├── CMakeLists.txt # CMake 构建入口,交叉编译时配合 toolchain 文件 ├── notepad.pro # qmake 入口,老工程或板厂 SDK 里常见 ├── src/ │ ├── main.cpp # 只做 QApplication 初始化、字体和翻译加载 │ ├── mainwindow.cpp # 界面组织、信号槽连接 │ ├── mainwindow.h │ ├── notestore.cpp # SQLite 或文件存储,不碰界面 │ └── notestore.h ├── resources/ │ ├── fonts/ # 中文字体,部署时拷到目标板 │ └── qss/ # 小屏深色主题 ├── deploy/ │ ├── run-notepad.sh # 启动脚本,设置平台插件和库路径 │ └── notepad.service # 需要开机自启时用 └── cmake/ └── toolchain-arm.cmake # 交叉编译工具链描述界面层只负责“显示什么”和“用户点了什么”,存储层负责“怎么落盘、怎么查询”,main.cpp只做应用级初始化。这样交叉编译出问题时,可以先单独编一个只带QApplication和空窗口的 demo,确认平台插件能起来,再逐步加回业务代码。CMake 入口可以写成下面这样,Qt5 和 Qt6 的差别主要在find_package的包名和链接目标:
cmake_minimum_required(VERSION 3.16) project(EmbeddedNotepad LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) # 有 Q_OBJECT 的类必须开,否则链接时报 vtable 未定义 set(CMAKE_AUTORCC ON) # 用到 .qrc 资源时打开 find_package(Qt5 REQUIRED COMPONENTS Widgets Sql) add_executable(notepad src/main.cpp src/mainwindow.cpp src/notestore.cpp ) target_link_libraries(notepad PRIVATE Qt5::Widgets Qt5::Sql) install(TARGETS notepad RUNTIME DESTINATION bin) install(DIRECTORY resources/fonts/ DESTINATION fonts) install(FILES deploy/run-notepad.sh DESTINATION bin PERMISSIONS OWNER_EXECUTABLE)CMAKE_AUTOMOC是 Qt 工程最容易漏的一项,漏了以后报错通常不是“找不到 moc”,而是链接阶段一堆undefined reference to vtable。find_package里的Widgets和Sql要跟实际用到的模块对齐,不用 SQLite 就别链Qt5::Sql,能少带一个库。install规则不是给主机用的,交叉编译时配合DESTDIR把文件收进一个部署目录,后面用 rsync 推到板子上,比手工拷文件可靠。
部署文件清单我一般按下面几类核对,少一类都可能出现“主机能跑、板子黑屏”:
| 类别 | 典型文件 | 检查点 |
|---|---|---|
| 可执行文件 | notepad | 用file确认是 ARM 架构 |
| Qt 基础库 | libQt5Core.so.5、libQt5Gui.so.5、libQt5Widgets.so.5 | 与编译时同一套 sysroot |
| 平台插件 | plugins/platforms/libqlinuxfb.so | QT_QPA_PLATFORM_PLUGIN_PATH指对 |
| SQL 驱动 | plugins/sqldrivers/libqsqlite.so | 用 SQLite 时必须有 |
| 字体 | 开源中文字体文件 | QT_QPA_FONTDIR指到目录 |
| 启动脚本 | run-notepad.sh | 设好库路径、平台、字体目录 |
提示:先确认源码包用的是 qmake 还是 CMake,不要两个构建文件同时改。qmake 工程在板厂 SDK 里更常见,CMake 工程更适合自己维护工具链文件;选一条路走通,另一条只做参考。
3. 交叉编译到目标板:工具链、sysroot 和首屏参数
3.1 先别 make:工具链、sysroot 和 ABI 三项核对
交叉编译翻车,一半以上是工具链和 sysroot 对不上。主机上能编出 x86 程序,不代表目标板能跑;目标板能跑一个 hello world,也不代表 Qt 库的 ABI 一致。我一般先做三件事:确认编译器目标架构、确认 sysroot 里的 Qt 库架构、确认浮点 ABI。下面这几条命令不涉及具体板卡型号,路径按自己的环境替换:
# 1. 看交叉编译器目标架构,输出里应有 arm 或 aarch64 arm-linux-gnueabihf-gcc -v # 2. 看 sysroot 里的 QtCore 是不是目标架构,Machine 应为 ARM arm-linux-gnueabihf-readelf -h /opt/target-sysroot/usr/lib/libQt5Core.so.5 | grep Machine # 3. 看浮点 ABI,硬浮点和软浮点不能混 arm-linux-gnueabihf-readelf -A /opt/target-sysroot/usr/lib/libQt5Core.so.5 | grep -i vfpreadelf -h里的Machine如果显示Advanced Micro Devices X86-64,说明你拿到的是主机库,部署到板子上必然报wrong format。readelf -A里看Tag_ABI_VFP_args,硬浮点工具链编出来的库和软浮点根文件系统混用,运行时会出现“非法指令”或直接段错误。板厂提供的工具链通常和根文件系统匹配,主机自带的交叉编译器不一定,能不用就不用。
sysroot 里还要确认 Qt 的安装路径。执行目标板版本的qmake -query,重点看QT_INSTALL_PREFIX、QT_INSTALL_LIBS、QT_INSTALL_PLUGINS和QT_SYSROOT:
/opt/target-sysroot/usr/local/qt-notepad/bin/qmake -query如果QT_SYSROOT为空,但你的编译命令又依赖 sysroot,后面找头文件和库会乱套。常见做法是在 configure 时显式传-sysroot,或者用工具链文件里的CMAKE_SYSROOT固定住。主机上如果还装着一套桌面 Qt,qmake很可能优先找到主机版本,编译出来的程序在板子上跑不起来;这时不要靠改PATH猜,直接在 CMake 里用CMAKE_PREFIX_PATH指向 sysroot 里的 Qt。
3.2 configure 参数逐项拆:linuxfb、tslib 和裁剪项
Qt 库的 configure 参数决定后面能不能起窗口。记事本这种工具不需要 WebEngine、不需要打印支持、不需要 CUPS,能裁就裁。下面是一组常见起点,路径和模块按实际源码包调整:
export TARGET_SYSROOT=/opt/target-sysroot export QT_SRC=/opt/src/qt-everywhere-src export CROSS_COMPILE=arm-linux-gnueabihf- export PATH=/opt/toolchain/bin:$PATH $QT_SRC/configure \ -prefix /usr/local/qt-notepad \ -sysroot $TARGET_SYSROOT \ -opensource -confirm-license \ -release -optimize-size \ -no-opengl -linuxfb -tslib \ -qt-zlib -qt-libpng -qt-libjpeg \ -no-feature-cups -no-feature-printsupport \ -nomake examples -nomake tests \ -skip qtwebengine-prefix是目标板上的安装路径,不是主机路径,后面部署脚本和LD_LIBRARY_PATH都要跟它一致。-sysroot让编译时找头文件和库都限制在目标根文件系统里,避免误链主机库。-release -optimize-size偏体积优化,适合存储紧张的板子;如果调试阶段需要符号,可以临时切到-debug,但别把调试版直接发到产线。-no-opengl -linuxfb表示走 framebuffer,不依赖 GPU;如果目标板有 GPU 且系统支持 eglfs,可以换成-eglfs,但不要在没有验证驱动的情况下硬套。
-tslib是触摸校准相关,老式电阻屏常用;电容屏且内核直接上报多点触摸事件时,可能不需要 tslib,具体看目标板输入子系统。-qt-zlib、-qt-libpng、-qt-libjpeg让 Qt 自带压缩和图片解码,减少对目标根文件系统里第三方库版本的依赖。-no-feature-printsupport对记事本影响不大,除非源码包里用了打印预览;-skip qtwebengine能省下大量编译时间和空间。裁剪的原则是“先保证 Widgets、Gui、Core、Sql 和平台插件可用”,其他模块按需加回。
编译 Qt 本身耗时较长,我一般会先编一个最小 Qt,只带 Widgets 和 linuxfb 插件,把空窗口在板子上跑起来,再回头补 SQLite 驱动和字体。这样出问题时边界清楚:黑屏就是平台插件或 framebuffer 的问题,不是业务代码。等空窗口稳定了,再编记事本工程,链接错误也更容易定位。
3.3 部署与启动:库、插件、字体一个都不能少
编译完不是把可执行文件拷过去就完事。Qt 程序启动时要找平台插件、图片插件、SQL 驱动和字体,任何一类缺失都可能表现为“没反应”或“黑屏”。我一般用DESTDIR把安装文件收进一个目录,再整体 rsync 到板子:
cmake -S . -B build-target \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm.cmake \ -DCMAKE_PREFIX_PATH=$TARGET_SYSROOT/usr/local/qt-notepad \ -DCMAKE_INSTALL_PREFIX=/usr/local/notepad cmake --build build-target -j4 DESTDIR=$PWD/deploy cmake --install build-target # 以下 IP 和路径只是示例,换成自己板子的地址和分区 rsync -av deploy/usr/local/notepad/ root@192.168.1.50:/usr/local/notepad/ rsync -av $TARGET_SYSROOT/usr/local/qt-notepad/lib/ root@192.168.1.50:/usr/local/qt-notepad/lib/ rsync -av $TARGET_SYSROOT/usr/local/qt-notepad/plugins/platforms/ root@192.168.1.50:/usr/local/notepad/plugins/platforms/CMAKE_TOOLCHAIN_FILE里要写清楚CMAKE_SYSTEM_NAME、CMAKE_SYSTEM_PROCESSOR、CMAKE_C_COMPILER、CMAKE_CXX_COMPILER和CMAKE_SYSROOT。CMAKE_PREFIX_PATH指向目标板 Qt 的安装前缀,不是主机 Qt。DESTDIR只影响安装阶段的根目录,不会改变程序里的-prefix,所以部署目录结构要和目标板路径对得上。
启动脚本建议单独放,不要把所有环境变量写进main.cpp。下面这版覆盖了平台、插件、字体和库路径:
#!/bin/sh export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0 export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/local/notepad/plugins/platforms export QT_QPA_FONTDIR=/usr/local/notepad/fonts export LD_LIBRARY_PATH=/usr/local/qt-notepad/lib:$LD_LIBRARY_PATH exec /usr/local/notepad/bin/notepadQT_QPA_PLATFORM里的fb=/dev/fb0要跟目标板实际的 framebuffer 设备一致,有的板子是/dev/fb1。QT_QPA_PLATFORM_PLUGIN_PATH指向libqlinuxfb.so所在目录,不是 Qt 安装目录的上一级。QT_QPA_FONTDIR只影响 Qt 找字体的目录,系统 fontconfig 是另一套路径。LD_LIBRARY_PATH要放在exec之前,否则程序启动时动态链接器已经找不到库了。
部署后用ldd查一遍缺失库,比在板子上看黑屏猜原因快得多:
LD_LIBRARY_PATH=/usr/local/qt-notepad/lib \ ldd /usr/local/notepad/bin/notepad | grep "not found"如果输出为空,说明动态库依赖齐了;如果有not found,按名字回推是 Qt 库、系统库还是编译器运行时库。常见漏项是libstdc++.so.6版本不匹配,主机工具链带的版本比板子根文件系统新,运行时报GLIBCXX_3.4.xx not found。这种不要往板子上硬塞主机库,优先用与根文件系统匹配的工具链重编。
4. 笔记功能落地:列表、编辑、自动保存和中文输入
4.1 主界面最小闭环:QListWidget 加 QTextEdit
记事本的主界面不需要复杂框架,左侧QListWidget管笔记列表,右侧QTextEdit管正文,中间用QSplitter或QHBoxLayout分开。小屏上我倾向固定左栏宽度,把更多空间留给编辑区,同时把列表项高度调大,方便手指点选。下面这段是构造函数的骨架:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { auto *central = new QWidget(this); auto *layout = new QHBoxLayout(central); layout->setContentsMargins(4, 4, 4, 4); layout->setSpacing(4); m_list = new QListWidget(central); m_list->setObjectName("noteList"); m_list->setFixedWidth(180); // 800x480 屏幕上左栏 180 左右,按实际分辨率调 m_list->setUniformItemSizes(true); // 列表项高度一致,滚动更轻 m_editor = new QTextEdit(central); m_editor->setAcceptRichText(false); // 先按纯文本做,存储和导出都简单 m_editor->setPlaceholderText(QStringLiteral("点这里开始记")); layout->addWidget(m_list); layout->addWidget(m_editor, 1); setCentralWidget(central); connect(m_list, &QListWidget::currentRowChanged, this, &MainWindow::loadNote); connect(m_editor, &QTextEdit::textChanged, this, &MainWindow::scheduleSave); }setFixedWidth(180)不是固定值,800x480 上可以 160 到 200,1024x600 上可以放到 240。setAcceptRichText(false)很关键,富文本会带来 HTML 解析、字体嵌套和存储膨胀,记事本场景没必要。setUniformItemSizes(true)让列表项高度一致,滚动时不用反复计算,低配板子上能省一点 CPU。
列表项不要只存标题字符串,要把笔记 id 塞进Qt::UserRole,否则标题重复时无法定位到具体笔记:
void MainWindow::reloadList() { const QSignalBlocker blocker(m_list); // 刷新列表时不要触发 currentRowChanged m_list->clear(); const auto notes = m_store->listNotes(50); for (const auto &n : notes) { auto *item = new QListWidgetItem(n.title, m_list); item->setData(Qt::UserRole, n.id); } }QSignalBlocker是刷新列表时的后悔药。不加它,clear()和addItem()会触发currentRowChanged,可能把正在编辑的内容覆盖掉。listNotes(50)只取最近 50 条,首屏不卡;要全部笔记时再做分页或搜索。Qt::UserRole存 id,显示文本只放标题,标题和 id 分开,后面重命名笔记时不会把主键改掉。
4.2 自动保存与异常恢复:QSaveFile 加防抖定时器
记事本最怕两件事:用户记完没点保存就切走,以及保存到一半掉电把原文件写坏。QSaveFile的做法是先写临时文件,commit()时再原子替换目标文件,掉电时旧文件至少还在。配合一个防抖定时器,用户停止输入 800 毫秒后再落盘,避免每敲一个字都写存储:
void MainWindow::scheduleSave() { if (m_loading) return; // 加载笔记时不要触发保存 m_dirty = true; m_saveTimer.start(800); // 800ms 防抖,触摸屏输入慢,别设 50ms } void MainWindow::saveCurrentNote() { if (!m_dirty || m_currentId.isEmpty()) return; QSaveFile file(notePath(m_currentId)); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { showToast(QStringLiteral("保存失败:%1").arg(file.errorString())); return; } QTextStream out(&file); out.setCodec("UTF-8"); // Qt6 改为 out.setEncoding(QStringConverter::Utf8) out << m_editor->toPlainText(); if (!file.commit()) { showToast(QStringLiteral("提交失败:%1").arg(file.errorString())); return; } m_dirty = false; m_store->touch(m_currentId, QDateTime::currentSecsSinceEpoch()); }m_loading是加载标志,loadNote()里先置 true,setPlainText()之后再置 false,防止“加载触发 textChanged、textChanged 又触发保存”的循环。m_saveTimer用单次触发,每次输入重新计时,800 毫秒是触摸屏上比较稳的值;设得太短,存储写入频繁,设得太长,切换笔记时容易丢最后一段。QSaveFile::commit()返回 false 一定要提示,不能只写日志,用户看不到日志就等于没保存。
如果源码包用的是 SQLite 而不是单文件,保存逻辑换成事务即可,但防抖和m_loading标志仍然保留。数据库路径要用可写分区,比如/userdata/notepad/notes.db,不要放在/usr/local/notepad/这种只读根文件系统下。启动时先QDir().mkpath()建目录,再打开数据库;打开失败要弹窗提示,不要静默降级到内存存储,否则用户以为保存了,重启全丢。
4.3 中文显示与触摸输入:字体、编码、软键盘三件事
中文显示和中文输入要分开处理。显示靠字体和编码,输入靠输入法或自绘键盘。先确认板子上有没有中文字体:
fc-list :lang=zh如果输出为空,界面上的中文大概率是方块。把开源中文字体拷到部署目录的fonts/下,启动脚本里设QT_QPA_FONTDIR=/usr/local/notepad/fonts,再在main.cpp里显式设置应用字体:
QFont font(QStringLiteral("Noto Sans CJK SC")); font.setPixelSize(18); // 小屏触摸,18 到 22 像素比较稳 qApp->setFont(font);字体名要和实际字体文件匹配,fc-list里能看到 family 名称。setPixelSize比setPointSize更适合固定分辨率的小屏,不受 DPI 缩放影响。编码方面,源码文件统一 UTF-8,QTextStream写文件时显式设 UTF-8,SQLite 里存的也是 UTF-8 字符串;不要用本地 locale 默认编码,板子上 locale 经常是C,中文会变问号。
触摸输入如果源码包自带软键盘,先看它是自绘键盘还是 Qt Virtual Keyboard。自绘键盘通常只插英文、数字和符号,逻辑简单,适合先跑通焦点和触摸:
void SoftKeyboard::insertText(const QString &text) { QWidget *focus = QApplication::focusWidget(); if (auto *edit = qobject_cast<QLineEdit *>(focus)) { edit->insert(text); } else if (auto *textEdit = qobject_cast<QTextEdit *>(focus)) { textEdit->insertPlainText(text); } }QApplication::focusWidget()拿到当前焦点控件,qobject_cast判断是单行还是多行编辑,再插入文本。这个方案不处理拼音候选,只适合英文和数字。中文输入常见做法是部署 Qt Virtual Keyboard 的拼音插件,或者调用系统输入法框架;前者要额外 QML 运行时和词库,后者要看根文件系统里有没有现成组件。我的建议是先用自绘键盘验证触摸、焦点和遮挡关系,再决定中文方案,不要一开始就把完整拼音引擎塞进去。
注意:软键盘弹出时会遮住编辑区,Widgets 里可以在键盘显示时给编辑区加底部边距,或者把编辑区滚动到光标位置。这个细节不处理,用户输入时看不到自己打的字,体验会直接崩。
5. 避坑与排查:Qt 嵌入式记事本五类翻车现场
5.1 黑屏、花屏与平台插件加载失败
现象:程序启动后没有窗口,串口没有明显报错;或者屏幕出现花屏、颜色错位。原因通常是QT_QPA_PLATFORM没设、平台插件路径不对、framebuffer 设备不对,或者 eglfs 在无 GPU 的板子上硬跑。先开插件调试,把加载过程打出来:
export QT_DEBUG_PLUGINS=1 export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0 ./notepad 2>&1 | tee /tmp/qt.log日志里重点看QFactoryLoader有没有找到libqlinuxfb.so,以及Cannot load library后面的原因。如果提示找不到平台插件,检查QT_QPA_PLATFORM_PLUGIN_PATH是否指到plugins/platforms目录,目录里是否有目标架构的.so。花屏则看/proc/fb和fbset,确认分辨率、色深和程序假设一致;程序里如果写死了 800x480,实际屏幕是 1024x600,布局会错位,但不一定花屏。颜色错位常见于 framebuffer 的 BGR/RGB 顺序,先换平台参数或检查内核配置,不要急着改业务代码。
5.2 中文方块、乱码和输入法不弹
现象:界面中文显示为方块,或者保存后的文件用十六进制看是 GBK 编码,换到主机打开乱码。原因分三层:字体缺失、源码/运行时编码不对、QTextStream用了本地编码。先查字体,再查编码。fc-list :lang=zh为空就部署中文字体;有字体但界面还是方块,检查QT_QPA_FONTDIR和QFont的 family 名是否匹配。文件乱码则把写入代码改成显式 UTF-8,Qt6 里setCodec已移除,要用setEncoding(QStringConverter::Utf8)。数据库连接也要确认存的是 UTF-8 文本,不要把QByteArray直接当字符串塞进去。
输入法不弹,先看焦点控件有没有设置Qt::StrongFocus,再看软键盘是不是只对QLineEdit生效。QTextEdit的焦点策略默认可用,但如果外面套了QScrollArea或自定义控件,焦点可能被吃掉。排查时在focusChanged信号里打印当前焦点控件类名,确认点击编辑区后焦点真的过去了。中文输入还要确认输入法插件是否部署,QT_IM_MODULE环境变量是否设置;没有输入法引擎时,自绘键盘只能插英文,不要误判为“键盘坏了”。
5.3 保存失败、文件丢失和自动保存死循环
现象:点保存没反应,日志里Permission denied;或者 CPU 占用突然飙高,界面卡住。前者的原因通常是数据库或笔记文件放在只读根文件系统,或者当前工作目录不可写;后者多半是textChanged里调用了setPlainText(),触发递归。保存路径要显式指到可写分区,不要用相对路径notes.db,因为启动脚本的当前目录不一定是部署目录。`QSaveFile
本文还有配套的精品资源,点击获取