Day 4

Qt 消息机制和事件 / 绘图事件及表格事件 / 文件系统

C++/Qt 方向实训 · 第 4 / 4 个讲授日

  • 上午(08:30–11:00 讲 / 11:00–12:00 练):事件循环与派发 · 事件拦截 · 绘图事件 · 表格
  • 下午(13:30–16:00 讲 / 16:00–17:00 作业):文件系统(QFile/QTextStream/QDataStream + JSON)

今晚的作业「本地数据存储小工具」是明天 Day5 综合案例的直接前身。

上午 · 讲授 08:30–11:00

事件机制 / 绘图事件 / 表格事件

事件循环与派发:事件 vs 信号

核心概念

  • Day2 的 app.exec() 今天拆开看:事件循环不停从队列取 QEvent 派发给目标控件——事件是框架推给控件的通知(鼠标/键盘/重绘),信号槽是 对象间的主动通知,两套机制并存不冲突。

常见的坑(明天的伏笔)

  • 主线程做耗时操作 = 事件循环停摆 = 整个界面冻结——点不动、拖不动、白屏。 这是学员项目里最高频的"卡死"来源,正确解法(多线程)明天综合案例专门讲。

出处:p03/ch02/minimal_qt_app.cppexec()

事件拦截:重写 xxxEvent vs 事件过滤器

核心概念

  • 两条拦截路径:重写 mousePressEvent() 等虚函数(只影响自己); installEventFilter()(一个对象统一拦截多个控件的事件)。 事件有 accept()/ignore() 状态,决定是否继续向父控件传播。

常见的坑

  • 重写事件函数忘记调用基类实现(QWidget::mousePressEvent(e))—— 控件默认行为悄悄丢失,现象诡异难查。

出处:p03/ch09/event_override_customwidget.cpp · event_filter_mainwindow.cpp · mouse_event.cpp

绘图事件:QPainter

核心概念

  • Qt 绘制是框架驱动而非"想画就画":所有绘制只能发生在 paintEvent() 里,由 Qt 按需调用;数据变了想重画,调 update()(异步排队一次重绘请求)。 绘图设备三件套 QImage/QPixmap/QPicture 作参考。

常见的坑

  • paintEvent 之外直接构造 QPainter 画窗口——未定义行为;
  • 数据变了忘调 update()——界面不会自动重画。

出处:p03/ch10/qpainter_basic.cpp (设备类参考:paint_device_qimage.cpp 等)

表格:QTableWidget 与单元格事件

核心概念

  • item 类控件:数据直接塞进控件(QTableWidgetItem,new 出来交给表格 接管所有权),上手快、中小项目够用;"表格事件"以信号暴露: cellClicked / cellDoubleClicked / itemChanged

钩子 · 它背后是完整的 Model/View 架构

  • 数据(model)与展示(view)彻底分离、同一模型可喂多个视图(QListView/QTableView); 改模型必须发 dataChanged 等信号视图才会刷新。数据规模大/多视图联动时的 正解,自学入口:examples/model_view/

出处:examples/table_widget/qt_table_widget_basics.cppREADME

上午 · 课堂练习 11:00–12:00(工程师指导、答疑)

动手:拦事件 + 画图形

任务卡 1 · 事件过滤器 + 自绘图形

目标
① 给一个控件装事件过滤器,拦截鼠标按下并打印坐标; ② 在 paintEvent 里画一个组合图形(棋盘格或仪表盘刻度)
验收标准
点击控件终端能看到坐标输出;图形正确绘制(可截图验证)
基础知识 · 参考
p03/ch09/event_filter_mainwindow.cpp · p03/ch10/qpainter_basic.cpp

下午 · 讲授 13:30–16:00

文件系统:QFile / 流 / JSON

QIODevice 统一读写抽象

核心概念

  • QFile/QTextStream/QDataStream 都建立在 QIODevice 统一接口上——之后遇到网络/串口也是同一套; QTextStream 面向文本行,QDataStream 面向结构化二进制。

常见的坑

  • open() 返回值不检查——文件不存在/没权限时静默失败, 后续读写全是空操作;
  • 文本/二进制模式混淆会破坏内容。

出处:p03/ch11/qfile_basic.cpp · qtextstream_io.cpp · qdatastream_io.cpp

JSON 解析与写入

核心概念

  • 序列化/反序列化是一对互逆操作,正确性标准是 round-trip:写出去再读回来 数据一致;QJsonDocument/Object/Array/Value 四件套, QJsonValue 是能装多种类型的变体。

常见的坑

  • 不检查 QJsonParseError:格式坏的输入静默变成空文档
  • 类型不匹配时 toInt()/toString() 不报错只返回默认值—— 数据悄悄丢,很难查。

出处:examples/json_io/qt_json_parse_write.cppREADME

★ 相对路径与工作目录


QFile bad("data.json");                       // ✗ 相对当前工作目录——它在哪取决于你怎么启动
QFile good(QCoreApplication::applicationDirPath() + "/data.json");   // ✓ 锚定可执行文件位置
    

核心概念

  • 相对路径基于进程当前工作目录——不是源码目录,也不一定是可执行文件目录; IDE、终端、脚本三种启动方式的工作目录可能各不相同。正解: applicationDirPath() 拼路径,或只读资源走 .qrc(昨天的资源卡)。

常见的坑

  • "IDE 里能读到文件,命令行跑就说不存在"——几乎每届学员必问,根源就是这页。

★ 文本编码再遇(文件版)


QTextStream ts(&file);
ts.setCodec("UTF-8");   // Qt 5 默认编码随平台/区域设置而变——跨机器必须显式指定
    

核心概念

  • Day2 界面乱码(QString/源码编码)和今天文件乱码是同一根源的两次出现: 字节 ↔ 文字之间永远隔着一层"编码",跨边界(源码/文件/网络)就必须显式指定。

常见的坑

  • Windows 记事本写的文件 Linux 程序读出乱码(或反过来)——不是"文件坏了", 是两边默认编码不同;养成条件反射:编码要显式指定

下午 · 课堂作业 16:00–17:00(工程师答疑)

作业:本地数据存储小工具

任务卡 2 · 本地数据存储小工具

目标
设计「学员信息」结构(姓名/成绩/标签),支持增加记录并保存到 JSON 文件, 重启程序后能读出之前保存的全部记录
验收标准
至少能存 2 条以上记录并在重启后完整读回;字段不丢失;数据文件路径用 applicationDirPath() 拼(今天的坑卡现学现用)
基础知识 · 参考
examples/json_io/ · p03/ch11/qfile_basic.cpp

当日产出验证

验收

现场演示「写入 → 重启 → 读回」完整闭环;讲得出 open()/ParseError 两处检查为什么不能省。

返回目录 · ← 上一天 · 下一天 →