Day 4

Qt 桌面全景

Qt 方向实训 · 第 4 / 4 天 · 讲:练 ≈ 1:2.7

  • 模块 A(3 课时):项目结构 / 第一个程序 / 信号槽 / 主窗口
  • 模块 B(3 课时):对话框 / 控件 / 事件 / 绘图 / 文件(速览 + 分组选做)

定位声明: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

项目结构 / 信号槽 / 主窗口

  • 最小 Qt 程序结构
  • 控件创建与显示
  • 内置 / 自定义 / lambda 信号槽
  • QMainWindow 中心部件

最小 Qt 程序:QApplication + exec()


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);
    

出处:p03/ch03/button_creation.cpp

信号槽:内置 / 自定义 / 重载消歧义


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

QMainWindow:中心部件


class DemoMainWindow : public QMainWindow {
    Q_OBJECT
public:
    DemoMainWindow(){
        QTextEdit* edit = new QTextEdit(this);
        setCentralWidget(edit);   // QMainWindow 有固定布局:菜单栏/工具栏/中心部件/停靠窗口/状态栏
    }
};
    

出处:p03/ch05/central_widget.cpp;停靠窗口见 dock_widget.cpp

模块 A · 练习 95min

动手:第一个 Qt 程序

任务卡 1 · 跑通最小程序(离屏验证)

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 后自动退出)。

任务卡 2 · 自定义信号槽改文本

仿 custom_signal_slot.cpp, 给一个按钮连接自定义信号槽:点击后改变一个 QLabel 的文本内容。

任务卡 3 · 窗口骨架

QMainWindow + dock_widget.cpp 搭一个「中心区 + 侧边栏」 的窗口骨架。

模块 B · 讲授 35min(五个方向各约 7min 速览)

对话框 / 控件 / 事件 / 绘图 / 文件

只求「这个方向能做什么」的骨架认知,不逐行讲解

对话框:文件对话框 + QTextStream


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

常用控件:QLabel 显示图片


QPixmap pixmap;
pixmap.load(":/Image/boat.jpg");   // 从 Qt 资源系统(.qrc)加载
QLabel* label = new QLabel(this);
label->setPixmap(pixmap);
    

出处:p03/ch08/label_pixmap.cpp

事件机制:重写鼠标事件


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

绘图:QPainter


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 读文本


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.cppqdatastream_io.cpp

模块 B · 练习 95min(三选一,分组完成)

动手:做一个可跑的小工具

方案 A · 图片查看器雏形

结合 label_pixmap.cpp + file_dialog.cpp: 用文件对话框选一张图片,加载到 QLabel 显示。

方案 B · 简易绘图板

结合 qpainter_basic.cpp + mouse_event.cpp: 监听鼠标拖动事件,记录轨迹点,在 paintEvent 里用 QPainter 画出轨迹。

方案 C · 文本文件小工具

结合 qfile_basic.cpp + qtextstream_io.cpp: 读一个文本文件,统计行数/字符数,把统计结果写回一个新文件。

当日产出验证 · 实训总验收

现场演示

QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
# 有 X 环境:去掉 QT_QPA_PLATFORM=offscreen 直接看窗口

每组演示自己选做的方案(A/B/C 三选一),教师验收「编译通过 + 交互符合预期」。

返回目录