Qt 方向实训 · 第 4 / 4 天 · 讲:练 ≈ 1:2.7
定位声明:Day1–3 是「强化已知」,今天是「Qt 零基础入门」——讲授压缩为 骨架级讲解,练习是「跟着例子改」而不是从零写。9 个章节、29 个目标不追求全覆盖。
tools/install_qt_host.sh
cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON \
-DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64 -DCMAKE_BUILD_TYPE=Debug
cmake --build build
模块 A · 讲授 35min
class Widget : public QWidget {
Q_OBJECT // 用到信号槽/属性系统的类都要有这个宏(触发 moc 代码生成)
public:
Widget(QWidget* parent = nullptr) : QWidget(parent) {}
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv); // 整个程序唯一的 QApplication
Widget w;
w.show();
return app.exec(); // 进入事件循环,程序在这里“活着”
}
出处:p03/ch02/minimal_qt_app.cpp(注意仓库版本额外注入了
QTimer::singleShot 200ms 自动退出,便于离屏自动化验证,真实项目不需要这一行)
// 第一种:先构造再设属性
QPushButton* btn = new QPushButton;
btn->setParent(this);
btn->setText(QStringLiteral("德玛西亚"));
btn->move(100, 100);
// 第二种:构造时指定文本与父亲
QPushButton* btn2 = new QPushButton(QStringLiteral("孙悟空"), this);
class Teacher : public QObject {
Q_OBJECT
signals:
void hungry(); // 无参信号
void hungry(QString food); // 带参信号(重载)
public slots:
void classIsOver(){ emit hungry(); emit hungry(QString("汉堡")); }
};
// 连接重载信号时必须用 QOverload 显式指定版本
connect(t, QOverload<>::of(&Teacher::hungry), s, QOverload<>::of(&Student::treat));
connect(t, QOverload<QString>::of(&Teacher::hungry), s, QOverload<QString>::of(&Student::treat));
出处:p03/ch04/custom_signal_slot.cpp; lambda 槽 + 值捕获/引用捕获对比见 lambda_signal_slot.cpp
class DemoMainWindow : public QMainWindow {
Q_OBJECT
public:
DemoMainWindow(){
QTextEdit* edit = new QTextEdit(this);
setCentralWidget(edit); // QMainWindow 有固定布局:菜单栏/工具栏/中心部件/停靠窗口/状态栏
}
};
模块 A · 练习 95min
QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app
QT_QPA_PLATFORM=offscreen tools/run_qt.sh button_creation
有 X 环境的同学可去掉 QT_QPA_PLATFORM=offscreen 直接看窗口弹出(200ms 后自动退出)。
仿 custom_signal_slot.cpp,
给一个按钮连接自定义信号槽:点击后改变一个 QLabel 的文本内容。
用 QMainWindow + dock_widget.cpp 搭一个「中心区 + 侧边栏」
的窗口骨架。
模块 B · 讲授 35min(五个方向各约 7min 速览)
只求「这个方向能做什么」的骨架认知,不逐行讲解
void openFile() {
QString path = QFileDialog::getOpenFileName(this, QStringLiteral("打开文件"));
if (path.isEmpty()) return;
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { /* 提示失败 */ return; }
QTextStream in(&file);
QString text = in.readAll();
QMessageBox::information(this, QStringLiteral("内容"), text);
}
出处:p03/ch06/file_dialog.cpp;模态/非模态对话框见 modal_dialog.cpp
QPixmap pixmap;
pixmap.load(":/Image/boat.jpg"); // 从 Qt 资源系统(.qrc)加载
QLabel* label = new QLabel(this);
label->setPixmap(pixmap);
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
void paintEvent(QPaintEvent*) override {
QPainter painter(this);
painter.drawLine(QLine(10, 10, 200, 10));
painter.drawRect(QRect(10, 30, 100, 50));
painter.drawEllipse(QRect(150, 30, 100, 80));
}
所有绘制都发生在 paintEvent 里,由 Qt 在需要重绘时自动调用。
出处:p03/ch10/qpainter_basic.cpp
QFile file("in.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "打开失败"; return -1; }
while (!file.atEnd()) { qDebug() << file.readLine(); }
QFileInfo info(file);
qDebug() << info.baseName() << info.suffix();
出处:p03/ch11/qfile_basic.cpp;结构化读写见 qtextstream_io.cpp、 qdatastream_io.cpp
模块 B · 练习 95min(三选一,分组完成)
结合 label_pixmap.cpp +
file_dialog.cpp:
用文件对话框选一张图片,加载到 QLabel 显示。
结合 qpainter_basic.cpp +
mouse_event.cpp:
监听鼠标拖动事件,记录轨迹点,在 paintEvent 里用 QPainter 画出轨迹。
结合 qfile_basic.cpp + qtextstream_io.cpp: 读一个文本文件,统计行数/字符数,把统计结果写回一个新文件。
QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
# 有 X 环境:去掉 QT_QPA_PLATFORM=offscreen 直接看窗口
每组演示自己选做的方案(A/B/C 三选一),教师验收「编译通过 + 交互符合预期」。
4 天实训到此结束——大纲: docs/teaching/OUTLINE.md