# 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.cpp（`exec()`）

### 事件拦截：重写 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.cpp （README）

上午 · 课堂练习 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.cpp （README）

### ★ 相对路径与工作目录 讲

    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 两处检查为什么不能省。

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