C++/Qt 方向实训 · 第 3 / 5 天 · 讲:练 ≈ 1:2.7
模块 A · 讲授 35min
QAbstractItemModel
及其子类)只负责存数据、回答"这个位置是什么",视图(QListView/
QTableView/...)只负责画——同一份模型可以同时喂给多个不同视图。QModelIndex(行/列/父项)寻址数据,视图向模型要数据时
还要带上"角色"(Qt::DisplayRole 显示文本、Qt::DecorationRole
图标……),同一个位置可以按不同角色返回不同内容。dataChanged/beginInsertRows+
endInsertRows),视图不会自动刷新,界面和数据"对不上"。QStandardItemModel 却又想自定义复杂数据结构:应该继承
QAbstractItemModel 自己实现,而不是硬塞进 Item 模型。
// 模型:只管数据
auto* model = new QStandardItemModel(this);
model->appendRow(new QStandardItem(QStringLiteral("需求分析")));
// 视图:只管展示,不持有数据
auto* view = new QListView(this);
view->setModel(model);
// 选中状态变化 → 通知外部,模型和视图之间没有直接耦合
connect(view->selectionModel(), &QItemSelectionModel::currentChanged, ...);
出处:docs/teaching/examples/model_view/qt_model_view_basics.cpp
(配套 README)——
仓库 p03/ 的 wiki 抓取内容没有 Model/View,这是本轮补写的教学补充示例,已离屏验证跑通。
app.exec() 进入的就是事件循环(复用
Day2 概念)——鼠标点击、
键盘输入、定时器触发都会变成"事件"(QEvent),被塞进这个循环里依次分发给
对应控件。event()/xxxEvent() 虚函数或事件过滤器;信号槽是"对象之间"
主动通知,走 connect()。控件收到事件后,常见做法就是在事件处理函数里
emit 一个信号,把"底层事件"转成"业务语义通知"。app.exec() 进入的就是事件循环——鼠标点击、键盘输入、定时器
触发都会变成事件,被塞进这个循环里依次分发。今天讲的"事件过滤器"就是在
事件被分发给控件之前,插一个"拦截点"。
xxxEvent()(只影响这一个控件自己)或装事件过滤器
installEventFilter()(可以在事件到达目标控件之前,由另一个对象统一拦截,
常用于"一个地方管多个控件"的场景)。accept()/ignore() 状态:处理完事件要表明"我
吃掉了这个事件"还是"继续往上传",决定事件是否会继续传播给父控件。xxxEvent() 时忘记调用基类实现(如 QWidget::mouseMoveEvent),
可能丢失控件本身需要的默认行为。
class EventLabel : public QLabel {
Q_OBJECT
public:
explicit EventLabel(QWidget* p=nullptr):QLabel(p){ setMouseTracking(true); }
protected:
void mouseMoveEvent(QMouseEvent* event) override {
setText(QString("Move: (%1, %2)").arg(event->x()).arg(event->y()));
}
};
重写 xxxEvent 是 Qt 事件处理的主线之一(另一条线是事件过滤器
installEventFilter)。出处:p03/ch09/mouse_event.cpp;
事件过滤器见 event_filter_mainwindow.cpp
模块 A · 练习 95min
qt_model_view_basics.cpp 里的 QListView 换成
QTableView,模型代码不改event_filter_mainwindow.cpp,给一个控件装事件过滤器,拦截鼠标按下事件并打印坐标模块 B · 讲授 35min
QFile/QTextStream/QDataStream 都建立在
QIODevice 这一统一读写接口上——同样的"打开/读/写/关闭"套路,以后遇到网络
套接字/串口一类设备也是同一套接口,学一次可以迁移到别的设备类型。QTextStream 面向"文本行"(人可读),QDataStream 面向
"结构化二进制"(保留类型信息,程序间高效互传但人不可直接读)——按需求选,不是谁更好。open() 的返回值:文件不存在/无权限时静默失败,后续读写在一个
没打开的设备上操作,容易读出空数据而不报错。QIODevice::Text):文本模式下 Qt 会做换行符
转换,用来读写二进制数据会破坏内容。
QFile file("in.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "打开失败"; return -1; }
while (!file.atEnd()) { qDebug() << file.readLine(); }
出处:p03/ch11/qfile_basic.cpp;结构化读写见 qtextstream_io.cpp、 qdatastream_io.cpp
QJsonDocument::toJson)
把内存数据变成可存/可传的文本,反序列化(fromJson)再变回来——正确性的标准是
"写出去再读回来,数据要一致"(round-trip)。QJsonValue 是一个变体(variant)类型,能装字符串/数字/布尔/数组/对象,
取值时要按预期类型调用 toString()/toInt() 等。QJsonDocument::fromJson 的 QJsonParseError:
文本格式错误时返回一个空文档,后续取值全是默认值,不会崩溃也不会报错,很难定位。toInt()/toString() 等不会抛异常,只会静默
返回默认值(0/空串),排查时容易误以为"数据没写进去"。
QJsonObject student;
student["name"] = QStringLiteral("张三");
student["score"] = 92;
QJsonDocument doc(student);
QByteArray bytes = doc.toJson(QJsonDocument::Indented); // 序列化
QJsonDocument parsed = QJsonDocument::fromJson(bytes); // 反序列化
QJsonObject obj = parsed.object();
qDebug() << obj["name"].toString() << obj["score"].toInt();
出处:docs/teaching/examples/json_io/qt_json_parse_write.cpp
(配套 README)——
仓库 p03/ch11 的 wiki 抓取内容只有 QFile/QTextStream/QDataStream,没有 JSON,
这是本轮补写的教学补充示例;不需要 GUI,tools/run_qt.sh qt_json_parse_write
直接跑即可,不需要 QT_QPA_PLATFORM=offscreen。
模块 B · 练习 95min(本地数据存储案例,练习任务,非预建方案)
qt_json_parse_write.cpp 的 JSON 数据实际写入一个文件(结合
qfile_basic.cpp 的 QFile 写法),再读回来解析校验.json 文件,内容可读;重新运行程序能正确解析出
之前写入的数据现场演示本地数据存储小工具,验证"写入 → 重启 → 读回"完整闭环。
明天 Day4:2D 图形绘制 / 多线程 / 动画框架与状态机 —— 大纲:docs/teaching/OUTLINE.md