
简介一份面向 Qt 初学者的个人记账本教学案例使用 Qt5.9.9 和 MinGW32 构建可在 Windows 10/11 与 Ubuntu 18.04 等系统下直接运行。案例围绕记账本的新增、删除、查询、统计等核心功能展开完整演示了 Qt 界面布局、信号与槽、事件处理、模型/视图、数据持久化及样式表等关键知识点并对本地文件存储与数据库存储的不同选择进行了说明适合课堂演示、课后练习也适合有 C 基础的学习者作为课程设计参考。资源包体积仅 17KB共 8 个文件包括 3 个 cpp 源文件、2 个头文件、Qt 工程文件和 README 说明文档代码量不大但结构清晰、模块划分明确便于逐行阅读和二次扩展能直观看到数据管理模块与主窗口的协作方式。目前已有 275 人学习下载。通过该项目可重点理解记账数据如何存储、检索与展示并学习跨平台 Qt 工程在 Windows/Linux 下的目录组织与编译配置是一份轻量实用的 GUI 开发入门资料。 很多学 Qt 的朋友做完几个控件小 Demo 之后就卡住了——Hello World 会写信号槽会用但真要独立交付一个完整的软件总觉得哪哪都不对。我这些年带过不少 Qt 新手也做过企业内部的技术培训发现最适合拿来当从入门到完整交付过渡项目的就是个人记账本。界面、数据库、图表、事件流转、打包发布全都能在一套代码里串起来而且业务逻辑足够简单不需要你边写代码边补业务知识。这篇文章就把这个教学案例的完整拆解写出来从环境选型到数据层设计再到界面交互、图表可视化和发布避坑一套链路走完。1. 为什么记账本能把 Qt 从未入门带到能交付1.1 一个项目覆盖 Qt 八成核心知识点计算器项目练的是控件摆放画图板项目练的是绘图事件播放器项目又一下跳到音视频解码难度曲线直接起飞。记账本的难度曲线非常平滑它天然包含了一套桌面软件最常见的完整结构界面录入、数据存储、列表展示、统计汇总。你在学习过程中真正要面对的恰好就是 Qt 日常开发里最常用的那部分能力而不是某个偏门模块。这个项目里你至少会遇到这些核心知识点窗口和布局管理QMainWindow、QWidget、QVBoxLayout、常用控件QTableWidget、QLineEdit、QComboBox、QDateEdit、QLabel、信号槽的完整链路、SQLite 数据库操作QSqlDatabase、QSqlQuery、列表数据刷新、用 QSS 做界面美化以及最后用 windeployqt 做发布打包。如果加上图表统计还会用到 QCustomPlot 或者 Qt Charts 这一层可视化方案。一个项目把界面、数据、事件、可视化、部署全走一遍这是能交付和会写控件之间最重要的分水岭。很多人在面试时说做过 Qt 项目结果一问数据是怎么存的、程序在别人电脑上能不能跑起来就答不上来就是因为之前的练习项目根本没走到这一步。1.2 难度分级设计教学节奏更好控记账本还特别适合分阶段教学不同基础的学员都能有自己的目标。基础版只要做到账目新增、删除、修改、列表展示和简单的金额统计纯粹练控件和数据库操作。标准版加上分类筛选、日期范围查询再用 QCustomPlot 画出支出趋势和分类占比图这一档已经是一个可以自用的记账工具了。进阶版可以加预算提醒、CSV 导入导出、多账本切换甚至用 Model/View 架构重写列表部分往商业项目靠拢。我带培训的时候一般按四次课来排第一次搭工程和主界面第二次接 SQLite 做增删改查第三次做图表统计和界面美化第四次讲打包发布。每次课都有肉眼可见的产出学员的反馈比前面讲控件语法时要好得多。2. 环境选型5.15.2 Creator MSVC稳字当头2.1 版本和编译器的选择逻辑Qt 的版本选择是个老生常谈的问题。教学场景下我强烈建议用 Qt 5.15.2它是 5.x 系列里相当稳定的一个 LTS 版本网上的资料、博客、论坛问答数量最多。遇到问题你基本搜得到答案这一点在教学场景里比最新版本重要得多。Qt 6 当然值得学但作为从入门到交付的第一门课稳定和资料丰富才是优先考虑的。编译器方面Windows 上 MainWindow 会遇到 MSVC 和 MinGW 两个选择。我推荐 MSVC2019 64-bit。原因很简单后续如果项目要接入第三方库、做 Windows 原生功能或者需要调试更完整的调用栈MSVC 兼容性明显更好而且 Qt 官方对 MSVC 版本的预编译包更新也最积极。MinGW 的好处是安装体积小、不需要额外装 Visual Studio但发布时常常要多带 libgcc 和 libstdc 运行库新手在这上面踩的坑反而多。不少学员装完 Qt 之后发现 Qt Creator 里对应的 Kit 显示红色感叹号编译按钮是灰的多半就是只装了 Qt 的 MSVC 组件但机器上并没有安装 Visual Studio 的 Build Tools。MSVC 组件编译时需要调用微软的 C 编译器和 Windows SDK这些不是 Qt 安装包自带的。解决方式就是装 Visual Studio 或者单独装 VS Build Tools然后重启 Qt Creator。2.2 下载安装和组件勾选建议Qt 官方安装包下载速度经常让人怀疑人生国内直接用镜像源就好中科大、清华的 Qt 镜像都挺稳定。下载安装器之后在线安装时选择组件教学场景下组件不用贪多勾这些就够组件是否建议说明Qt 5.15.2 MSVC 2019 64-bit必选核心模块包含 Qt Widgets、SQL、Core、GUIQt Debug Symbols可选调试时定位更精准就是占磁盘空间Qt Sources可选想深入看源码再装Qt Creator必选官方 IDEQt Charts可选如果用 Qt Charts 做图表需要勾选MinGW 相关组件可不选使用 MSVC 方案时用不到2.3 Creator 还是 VS Code关于 Qt Creator 和 VS Code 的争论一直都有。VS Code 配 Qt 插件之后确实能写代码但教学场景下我更偏 Qt Creator因为它的 Designer 可视化编辑、Qt 帮助文档集成、断点调试、qrc 资源管理都是开箱即用的。学员不用折腾 launch.json、tasks.json 这些环境配置把精力集中在项目本身的逻辑上。性能实测方面Creator 在大项目上启动确实比 VS Code 稍慢但记账本这种体量的工程完全感觉不到差别。你如果已经习惯 VS Code当然也可以继续用装好 Qt VS Code 扩展之后配合 CMake 工程体验也不差。但对第一次接触 Qt 的学员来说Creator 的容错率更高省下来的配置时间干点正事不好吗。3. 先把数据层立住SQLite 表结构与 DbManager 封装3.1 为什么一上来就要谈数据层很多新手写代码的习惯是先画界面界面觉得差不多了再回头想数据怎么存。这个顺序在记账本这种小项目里勉强能跑但你会发现界面代码和 SQL 语句搅在一起函数越写越长改一个查询条件要翻半天。正确做法是先定数据层再把界面接到数据层上。数据层在主程序里一般体现为一个 DbManager 类负责数据库连接、建表、增删改查的 SQL 封装。界面层只需要调用addBill、deleteBill、loadBills这些方法不需要知道 SQLite 底层怎么执行。这样每次界面调整时只要接口不变数据层完全不用动。3.2 账目表结构设计SQLite 里建一张 bills 表就够了字段这么设计CREATE TABLE IF NOT EXISTS bills ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, category TEXT NOT NULL, amount REAL NOT NULL, date TEXT NOT NULL, note TEXT ); CREATE INDEX IF NOT EXISTS idx_bills_date ON bills(date);type 字段表示支出还是收入category 存分类名比如餐饮、交通、购物、工资这些。amount 用 REAL 存浮点金额。date 我用 TEXT 存而不像有些人想的用 DATE 类型。SQLite 本身是动态类型用 ISO 格式的字符串yyyy-MM-dd存日期天然支持字典序排列范围查询BETWEEN 2024-01-01 AND 2024-01-31完全没问题而且显示的时候直接就是字符串不用再做格式转换。按 date 建索引是因为后续最常见的查询是某时间范围内有多少笔支出这个索引能帮上忙。记账本数据量小这个索引效果未必明显但是让学员养成建索引的意识很有价值。3.3 DbManager 的核心实现DbManager 的初始化和建表逻辑长这样class DbManager { public: bool init(const QString dbPath); bool addBill(const QString type, const QString category, double amount, const QString date, const QString note); bool deleteBill(int id); bool updateBill(int id, const QString category, double amount, const QString note); QVectorBillInfo loadBills(const QString dateBegin, const QString dateEnd, const QString type, const QString category); private: QSqlDatabase m_db; }; bool DbManager::init(const QString dbPath) { m_db QSqlDatabase::addDatabase(QSQLITE); m_db.setDatabaseName(dbPath); if (!m_db.open()) { qWarning() Open database failed: m_db.lastError().text(); return false; } QSqlQuery query; return query.exec(CREATE TABLE IF NOT EXISTS bills (...)); }新增一条账目记录时务必用 prepared statement 而不是拼字符串bool DbManager::addBill(const QString type, const QString category, double amount, const QString date, const QString note) { QSqlQuery query; query.prepare(INSERT INTO bills(type, category, amount, date, note) VALUES (:type, :category, :amount, :date, :note)); query.bindValue(:type, type); query.bindValue(:category, category); query.bindValue(:amount, amount); query.bindValue(:date, date); query.bindValue(:note, note); return query.exec(); }这样除了防止 SQL 注入外还顺带免去了字符串里引号、特殊字符的转义噩梦。你想象一下如果备注里写了Im fine拼 SQL 字符串很可能会拼出语法错误而 prepared statement 完全没这个问题。数据库文件放哪也值得说一下。直接放程序当前工作目录最简单但发布后往往没有写权限或者路径里有中文引发各种奇怪问题。更好的位置用QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)这是系统为应用准备的用户数据目录各平台都能正确解析。教学阶段可以两套逻辑都留口子路径不存在就QDir().mkpath()创建。4. 主界面搭建与增删改查的完整信号槽链路4.1 主窗口控件组成主窗口用 QMainWindow中心组件放一个 QWidget 作为容器。整个界面从上到下分三个区域顶部是筛选条件区一行排开起始日期、结束日期、类型下拉框、分类下拉框、查询按钮。中间是账目列表区用 QTableWidget 展示列设置为日期、类型、分类、金额、备注最后一列放操作按钮或者用行选中后点击删除。底部是统计区三个 QLabel 分别显示筛选范围内的总支出、总收入、结余。筛选条件区用 QHBoxLayout 横向排列主布局用 QVBoxLayout左右留白不要太夸张整体看起来像正经桌面软件的样子就行。表格初始化的时候要设置列宽日期列 110 像素、类型和分类列 80 像素、金额列 100 像素、备注列拉伸占满剩余空间。horizontalHeader()-setStretchLastSection(true)可以让最后一列自动分配剩余宽度。4.2 新增一条账目的完整链路当用户点击新增账目按钮时完整的调用链路是这样的按钮的 clicked 信号连接到 MainWindow 的槽函数槽函数弹出录入对话框对话框里选择类型、分类填写金额、日期、备注点击确定后通过一个自定义信号把数据传回主窗口主窗口拿到数据先做校验再调用 DbManager 添加记录最后刷新表格和统计。校验这块容易偷懒但恰恰是教学重点。金额要校验是不是有效的正数日期要校验是不是合理的日期范围备注可以允许为空但长度做个限制。这些校验放在对话框的确定槽里不合法就弹 QMessageBox::warning不关闭对话框。很多初学者不写校验结果数据库里存进一堆脏数据后面图表统计的时候就会发现所有分类金额加起来对不上。录入界面本身可以用 QDialog也可以做得更轻量在主界面下方内嵌一个录入表单区。内嵌形式少一次弹窗交互操作更快界面也更好看。我常用后者学员普遍反馈这种即填即存即刷新的体验更有成就感。4.3 表格刷新、删除和修改的联动处理加载列表数据时先执行查询然后按结果数量设置表格行数。有个很常见的小坑每次刷新前要tableWidget-setRowCount(0)清空旧数据否则上一轮查询结果行会残留造成显示错乱。逐行填充数据时金额列可以用QTableWidgetItem设置右对齐并格式化抹平浮点误差ui-tableWidget-setItem(row, 3, new QTableWidgetItem(QString::number(amount, f, 2)));删除操作的正确姿势是先currentRow()拿到当前选中行的行号再从这一行数据里找出 id。所以我在加载数据时把 id 存在 item 的Qt::UserRole数据里而不是单独弄一列显示给用户看。修改功能我一般不在表格里直接编辑而是选中某一行之后点修改按钮把这一行的数据回填到录入表单里。这里最容易出错的点是类型字段是下拉框回填时要通过findText找到对应索引再setCurrentIndex而不是直接把字符串塞进去。更新完数据后用同样的方式刷新表格界面就能和数据库保持一致。4.4 界面美化QSS 让教学案例更像产品功能全部跑通之后加 QSS是提升项目完成度的关键一步。不需要设计得多花哨干净清爽就行。一个浅色背景、圆角输入框、表格隔行换色的方案就比默认风格的灰色方块好看太多。#mainWidget { background-color: #f7f8fa; } QTableWidget { background-color: white; border: none; border-radius: 8px; alternate-background-color: #fafbfc; gridline-color: #ececec; selection-background-color: #e8f0fe; selection-color: #202124; } QPushButton#addBtn { background-color: #3b7cff; color: white; border: none; border-radius: 6px; padding: 8px 24px; } QPushButton#addBtn:hover { background-color: #2b6ce3; }把 qss 文件放到资源文件 qrc 里用QFile读取再qApp-setStyleSheet加载比在代码里一行行 setStyleSheet 要清爽得多。教学时可以让学员自己配一套主题色这个环节他们通常玩得最起劲。5. 用 QCustomPlot 做出支出趋势图和分类占比图5.1 QCustomPlot 还是 Qt Charts做图表统计前先选工具。Qt Charts 是 Qt 官方模块安装时勾选就能用动画效果和交互反馈比较现代。但我个人更推荐教学项目用 QCustomPlot原因很实际它只有 qcustomplot.h 和 qcustomplot.cpp 两个文件直接拖进工程就能编译不需要额外模块依赖随时随地都能跑起来。QCustomPlot 虽然经常被用来做信号波形可视化比如时域波形转频域图那种场景但它的统计图表能力同样很能打。折线图、柱状图、饼图都有现成的类接口直白文档和示例代码也全。对记账本这种查询数据然后画出来的需求完全够用。加上它是开源的GPL 协议商用需要购买商业授权对教学来说没有任何成本门槛。我给的选型建议是教学用 QCustomPlot因为部署成本低、教育场景不存在协议风险如果后续做商业产品再考虑 Qt Charts 或者升级成 QCustomPlot 商业版。5.2 月度支出趋势折线图趋势图展示的是某段时间内每个月的支出总额变化。数据用 SQL 按月汇总SELECT substr(date, 1, 7) AS month, SUM(amount) FROM bills WHERE type 支出 AND date BETWEEN :begin AND :end GROUP BY month ORDER BY month;拿到数据之后往 QCustomPlot 里塞核心代码ui-trendPlot-clearGraphs(); QCPGraph *graph ui-trendPlot-addGraph(); QVectordouble xs, ys; for (int i 0; i months.size(); i) { xs i; ys amounts[i]; } graph-setData(xs, ys); graph-setPen(QPen(QColor(0, 160, 233), 2)); graph-setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 6)); QSharedPointerQCPAxisTickerText textTicker(new QCPAxisTickerText); textTicker-addTicks(xs, months); ui-trendPlot-xAxis-setTicker(textTicker); ui-trendPlot-xAxis-setTickLabelRotation(30); ui-trendPlot-rescaleAxes(); ui-trendPlot-replot();这里有个关键点x 轴放的并不是日期字符串而是 0、1、2 这样的索引再用 QCPAxisTickerText 把索引对应的月份标签映射到轴上。如果直接把字符串当 x 轴坐标QCustomPlot 处理不了类别轴图表就会乱。这个坑不少学员都踩过。rescaleAxes()会自动把坐标轴范围贴合数据范围别忘了在数据变化之后调用。最后一定要replot()QCustomPlot 不会自动重绘不调用这行界面就看不到新图表。5.3 分类支出占比饼图饼图用 QCustomPlot 的 QCPPie 类实现。它的逻辑是先创建饼图对象然后往里面一块块加数据QCPPie *pie new QCPPie(ui-piePlot-xAxis-range().center(), ui-piePlot-yAxis-range().center()); pie-setPen(QPen(Qt::NoPen)); pie-setLabelMode(QCPPie::lmPercent); // 显示百分比 for (int i 0; i categories.size(); i) { pie-addSlice(categories[i], amounts[i]); } ui-piePlot-addPlottable(pie); ui-piePlot-replot();默认情况下 QCPPie 会自动分配一组颜色但同一批数据顺序每次一致的话颜色也固定所以不用担心刷新时配色乱跳。如果你希望能手动控制分类的颜色可以拿第一个 slice 返回的QCPPieSlice指针然后逐个设置setBrush。饼图刷新的时机建议和表格联动当用户改了筛选条件并点击查询后refreshChart()和refreshTable()一起被调用。不要把查询逻辑写在窗口构造函数里只跑一次否则新增一条账目后图表完全不会更新初学者经常在这里困惑。5.4 图表数据刷新的时机与性能记账本的数据量一般不大每次查询后直接重新生成图表数据即可不用做增量更新。QCustomPlot 在几百个数据点范围内重绘性能绰绰有余。如果学员想扩展成按周/按月/按年多种统计粒度只需要替换 SQL 的GROUP BY字段代码框架完全不用动。数据库连接池这种东西在这个项目里是没必要的单用户本地应用单连接完全够用。这个话可以跟学员讲清楚让他们理解不同场景下技术选型差异不要一上来就过度设计。6. 打包发布避坑从我电脑上能跑到别人电脑上也能跑6.1 windeployqt 的正确打开方式项目做完之后第一件事是切到 Release 模式重新编译一遍确认没有 qDebug 输出干扰、没有依赖调试库。然后打开 Qt 提供的命令行工具切到 exe 所在目录执行cd /d D:\build-Ledger-Desktop_Qt_5_15_2_MSVC2019_64bit-Release\release D:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe Ledger.exewindeployqt 会扫描 exe 的依赖自动把 Qt 的 DLL、platforms 插件目录、styles 插件、翻译文件等复制到 exe 旁边。执行完之后你会看到一个几十兆的文件夹这就是最基础的发布形态。6.2 platform plugin 初始化失败这类报错的排查链路发布后在干净环境里双击 exe最常见的一个报错就是windows no qt platform plugin could be initialized, reinstalling the application may fix this problem。你按正常的路径应该是先检查 exe 同目录下有没有platforms文件夹里面有没有qwindows.dll。platforms文件夹必须和 exe 同级不能塞到子目录里更不能漏掉。如果 platforms 文件夹没问题接下来看 PATH 环境变量。Windows 上如果之前装过其他版本的 Qt或者 PATH 里残留了某个 Qt 的 bin 目录exe 运行时优先加载的 DLL 可能就不是同目录下的那批版本和编译器不一致就会触发 platform plugin 初始化失败。排查方法是把 exe 目录移到一台干净的机器或者一个隔离目录里再跑排除 PATH 干扰。另一个隐蔽的坑是 Release 和 Debug 混用。有的人平时一直用 Debug 构建发布时直接把 Debug 的 exe 拖出来拿到 windeployqt 来打结果把 debug 依赖的 Qt5Cored.dll 和 release 的 Qt5Core.dll 混在一起。Qt 的 debug 和 release 库不能混着用发布前务必重新构建 Release 版本。最直接的办法是在 Qt Creator 左下角的构建配置里切换成 Release然后重新编译。6.3 运行库和目录结构MSVC 编译的程序还需要 VC 运行库。目标机器上如果没有安装过 Visual C Redistributable程序可能根本起不来。最稳妥的做法是发布时把vc_redist.x64.exe一起附带写个安装脚本或者在压缩包里说明先安装运行库。windeployqt 默认不会自动带上 VC 运行库这一步很容易被忽略。发布目录里建议把数据库文件路径处理成首次启动自动创建而不是把开发机器上已经塞满测试数据的 ledger.db 直接打包给用户。我一般会在发布说明里加一句首次启动会在用户数据目录自动生成空白账本数据库。如果目标环境是国产 Linux 发行版要留意系统里自带的 Qt 版本和开发环境版本是否一致不一致时建议把项目用到的 Qt 插件和依赖库一并打包发布避免依赖系统库版本差异导致运行时崩溃。这类环境的坑比较深教学阶段可以在虚拟机里跑通一个流程给学员建立跨平台发布需要单独适配的意识就够了。我自己带项目的经验是打包发布这一关卡住的人最多。原因不是技术难而是这个环节没有报错定位工具全靠对 Qt 运行时结构有完整认知。所以课堂上我通常会让学员先把发布目录结构和每个文件夹的作用讲一遍再动手打包效果比直接扔给他们 windeployqt 命令好得多。最后分享一个教学小技巧让每个学员把自己的账本应用设计成不同主题色然后互相拷贝发布会遇到各种奇奇怪怪的问题大家一起排查一遍这种真实环境里的被迫动手比看十遍教程都管用。记账本这个案例能扩展的方向还有很多预算管理、账单提醒、CSV 导入导出、多账户支持每一个都是把现有代码结构往更工程化方向推进的好题目。先把这个项目完整交付出去你就算真正迈进 Qt 开发的门了。本文还有配套的精品资源点击获取