C++ 图形界面开发

本章将学习如何使用 Qt 框架进行 C++ 图形界面(GUI)开发。Qt 是一个功能强大、跨平台的 C++ 应用程序开发框架,被广泛应用于桌面软件、嵌入式系统和移动应用的开发。

本页阅读量: 加载中...

Qt 简介

通俗类比

Qt 就像一套精装房的装修工具包。你不用自己砌墙、铺电线(从零写窗口绘制),工具包里已经有了门窗(窗口)、开关按钮(控件)、水电管线(信号槽)。你只需要拼装和装饰,就能造出漂亮的房子(应用程序)。

Qt 是一个跨平台的 C++ 应用程序开发框架,由 Qt Company 开发。它提供了丰富的类库和工具,用于开发图形用户界面(GUI)程序以及非 GUI 程序(如命令行工具和服务器)。

为什么选择 Qt?

  • 跨平台:一次编写,可在 Windows、macOS、Linux、Android、iOS、嵌入式系统上运行
  • 丰富的控件:提供了按钮、文本框、表格、树形控件等大量现成的 GUI 组件
  • 信号与槽机制:创新的对象通信方式,替代了回调函数
  • 完善的文档:官方文档详细,社区活跃,学习资源丰富
  • 强大的工具:Qt Designer(界面设计)、Qt Creator(IDE)等
  • 开源免费:LGPL 开源协议,商业开发也可免费使用

Qt 的应用领域

领域典型应用
桌面软件WPS、VirtualBox、Autodesk Maya
嵌入式车载系统、工业控制、医疗设备
移动应用使用 Qt Quick 开发跨平台 App
游戏开发UI 界面、2D 游戏(Qt Quick)
科学可视化数据可视化、3D 渲染

Qt 的核心模块

模块说明
QtCore核心非 GUI 功能:事件循环、定时器、数据结构
QtGuiGUI 基础:窗口系统集成、事件处理、2D 图形
QtWidgets桌面 GUI 控件:按钮、标签、文本框等
QtNetwork网络编程:TCP/UDP 通信、HTTP 请求
QtSql数据库访问:SQLite、MySQL 等
QtMultimedia多媒体:音频、视频、摄像头

编程练习

调研并对比三种 C++ GUI 框架(Qt、wxWidgets、FLTK)的特点,列出各自的优势和适用场景。说明为什么 Qt 在跨平台开发中占有重要地位。

Qt 安装

通俗类比

安装 Qt 就像组装一套高级厨具。你需要下载安装包(买厨具)、选择组件(挑选锅碗瓢盆)、配置环境变量(把厨具放在厨房顺手的位置)。装好后,你就可以开始做菜(开发程序)了。

方法一:Qt 在线安装器(推荐)

  1. 访问 qt.io/download 下载 Qt Online Installer
  2. 运行安装器,需要注册 Qt 账号(免费)
  3. 选择安装路径(建议不含中文和空格)
  4. 在组件选择页面,勾选以下内容:
    • Qt 6.x.xMinGWMSVC(编译器)
    • Qt 6.x.xQt 5 Compatibility Module
    • Qt 6.x.xAdditional Libraries → Qt Charts, Qt Network
    • Developer and Designer Tools → Qt Creator(IDE),CMake,MinGW
  5. 点击安装,等待下载完成

方法二:包管理器(Linux)

bash
# Ubuntu/Debian
sudo apt install qt6-base-dev qt6-tools-dev qtcreator

# Fedora
sudo dnf install qt6-qtbase-devel qt-creator

# Arch Linux
sudo pacman -S qt6-base qt6-tools qtcreator
提示

如果硬盘空间有限,至少安装 MinGW 编译器 + Qt Creator + Qt Base 模块,其他组件可以后续通过维护工具补充安装。

验证安装

打开 Qt Creator,点击"新建项目"→"Application(Qt)"→"Qt Widgets Application",创建后按 Ctrl+R 运行。如果看到一个空白窗口,说明安装成功!

编程练习

完成 Qt 开发环境的安装与配置。创建一个新项目,确保项目能够成功编译并运行一个空白窗口。记录安装过程中遇到的问题及解决方法。

第一个 Qt 程序

通俗类比

写第一个 Qt 程序就像开一家小店。你需要一个门面(窗口 QWidget)、一个招牌(窗口标题)、一个店员处理顾客请求(事件循环 exec)。没有店员,店开起来也没人接待客人。

让我们创建一个简单的 Qt Widgets 应用程序,了解 Qt 程序的基本结构。

使用 Qt Creator 创建项目

  1. 打开 Qt Creator,点击 "新建项目"
  2. 选择 Application (Qt) → Qt Widgets Application
  3. 输入项目名称(如 FirstQtApp),选择保存路径
  4. 构建系统选择 CMakeqmake
  5. 类名保持默认 MainWindow,基类选择 QMainWindow
  6. 完成创建,按 Ctrl+R 运行项目

程序结构解析

C++ (main.cpp)
#include <QApplication>   // Qt 应用程序类
#include <QMainWindow>    // 主窗口类
#include <QLabel>         // 标签控件

int main(int argc, char *argv[])
{
    // 创建应用程序对象(每个 Qt 程序必须有且只有一个)
    QApplication app(argc, argv);
    
    // 创建主窗口
    QMainWindow window;
    window.setWindowTitle("我的第一个 Qt 程序");
    window.resize(400, 300);  // 设置窗口大小
    
    // 创建一个标签控件
    QLabel *label = new QLabel("Hello, Qt!", &window);
    label->setAlignment(Qt::AlignCenter);  // 居中对齐
    
    // 设置窗口中央部件
    window.setCentralWidget(label);
    
    // 显示窗口
    window.show();
    
    // 进入事件循环,等待用户操作
    return app.exec();
}

CMakeLists.txt 配置

cmake
cmake_minimum_required(VERSION 3.16)
project(FirstQtApp LANGUAGES CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 Qt 包
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)

# 创建可执行文件
add_executable(FirstQtApp
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
)

# 链接 Qt 库
target_link_libraries(FirstQtApp PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Widgets
)
说明

QApplication::exec() 进入 Qt 的事件循环,程序会一直运行直到用户关闭窗口。所有 GUI 程序都需要调用这个函数。Qt 使用 事件驱动的编程模型。

Qt 中的对象树机制

Qt 使用对象树(Object Tree)来管理内存。当你创建一个 QObject 并指定父对象时,它会被自动添加到父对象的子对象列表中。当父对象被销毁时,所有子对象也会被自动销毁。

C++
// 不需要手动 delete label,因为窗口销毁时会自动释放
QLabel *label = new QLabel("文本", &window);  // window 是父对象

// 或者使用 setParent
QPushButton *btn = new QPushButton("按钮");
btn->setParent(&window);  // 设置父对象

编程练习

基于第一个 Qt 程序,修改窗口属性:将窗口大小设为 640x480,标题改为"My First Qt App",背景色设为浅灰色。在窗口中央添加一个 QLabel 显示"Hello Qt"。

信号与槽

通俗类比

信号槽就像门铃和对讲机。按钮被按下(发出信号"叮咚"),窗口收到信号后通过对讲机(槽函数)回应"来了!"——两者不需要直接知道对方是谁,只需要连接在同一套门铃系统上。

信号与槽(Signals and Slots)是 Qt 最独特的特性之一,用于对象之间的通信。它是 Qt 对观察者模式(Observer Pattern)的实现,比传统的回调函数更安全、更灵活。

基本概念

  • 信号(Signal):当某个事件发生时发出的通知,如按钮被点击
  • 槽(Slot):接收到信号后执行的函数,用于处理信号
  • 连接(Connect):将信号和槽关联起来

连接信号与槽

C++
#include <QApplication>
#include <QPushButton>
#include <QLabel>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    
    QWidget window;
    window.setWindowTitle("信号与槽示例");
    window.resize(300, 200);
    
    QPushButton *btn = new QPushButton("点我", &window);
    btn->move(100, 80);
    
    // 连接信号和槽(新语法,C++11)
    QObject::connect(btn, &QPushButton::clicked, [&]() {
        qDebug() << "按钮被点击了!";
    });
    
    window.show();
    return app.exec();
}

connect() 函数详解

C++
// 语法:connect(发送者, 信号, 接收者, 槽)

// 方式1:新语法(类型安全,推荐)
connect(btn, &QPushButton::clicked, label, &QLabel::clear);
connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);

// 方式2:使用 lambda 表达式
connect(btn, &QPushButton::clicked, [&]() {
    int value = slider->value();
    label->setText(QString("当前值: %1").arg(value));
});

// 方式3:连接多个槽到一个信号
connect(btn, &QPushButton::clicked, [=]() { qDebug() << "槽1"; });
connect(btn, &QPushButton::clicked, [=]() { qDebug() << "槽2"; });

自定义信号与槽

C++ (自定义类)
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

class Counter : public QObject {
    Q_OBJECT  // 必须添加此宏,启用信号槽机制
    
public:
    explicit Counter(QObject *parent = nullptr) : QObject(parent), m_value(0) {}
    
    int value() const { return m_value; }
    
    // 槽函数
public slots:
    void setValue(int value) {
        if (m_value != value) {
            m_value = value;
            emit valueChanged(m_value);  // 发射信号
        }
    }
    
signals:
    // 自定义信号(不需要实现)
    void valueChanged(int newValue);
    
private:
    int m_value;
};

#endif
提示

使用信号与槽时,参数类型必须匹配。信号和槽的连接是松耦合的:发送者不需要知道接收者的存在,反之亦然。这使得代码更加模块化。

编程练习

创建一个窗口,包含一个 QPushButton 和一个 QLabel。使用信号槽机制实现:点击按钮时,QLabel 的文本在"已点击"和"未点击"之间切换。分别用两种语法实现(旧版 SIGNAL/SLOT 宏和新版指针语法)。

常用控件

通俗类比

控件就像家具。QPushButton 是开关(灯开关),QLabel 是告示牌,QLineEdit 是输入框(信箱投信口),QCheckBox 是复选框(菜单上的勾选)。把这些家具摆进房间(窗口),就形成了完整的居住空间(界面)。

Qt 提供了丰富的 GUI 控件(Widgets),下面是一些最常用的控件及其用法。

基础控件

控件类说明常用方法
QLabel标签,显示文本或图片setText(), setPixmap()
QPushButton按钮setText(), clicked 信号
QLineEdit单行文本输入框text(), setText()
QTextEdit多行文本编辑框toPlainText(), setPlainText()
QComboBox下拉选择框addItem(), currentText()
QCheckBox复选框isChecked(), setChecked()
QRadioButton单选按钮isChecked()
QSlider滑块value(), setRange()
QProgressBar进度条setValue(), setRange()
QListWidget列表控件addItem(), currentItem()
QTableWidget表格控件setItem(), setColumnCount()

控件使用示例

C++
#include <QWidget>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QComboBox>
#include <QCheckBox>
#include <QMessageBox>

class FormWidget : public QWidget {
    Q_OBJECT
public:
    FormWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // 创建布局
        QVBoxLayout *layout = new QVBoxLayout(this);
        
        // 标签
        QLabel *titleLabel = new QLabel("

用户信息

"
); layout->addWidget(titleLabel); // 单行输入框 QLineEdit *nameEdit = new QLineEdit(); nameEdit->setPlaceholderText("请输入姓名"); layout->addWidget(nameEdit); // 下拉框 QComboBox *genderBox = new QComboBox(); genderBox->addItem("男"); genderBox->addItem("女"); layout->addWidget(genderBox); // 复选框 QCheckBox *agreeBox = new QCheckBox("我同意用户协议"); layout->addWidget(agreeBox); // 提交按钮 QPushButton *submitBtn = new QPushButton("提交"); layout->addWidget(submitBtn); // 连接信号槽 connect(submitBtn, &QPushButton::clicked, [=]() { if (!agreeBox->isChecked()) { QMessageBox::warning(this, "警告", "请先同意用户协议!"); return; } QString msg = QString("姓名: %1\n性别: %2") .arg(nameEdit->text()) .arg(genderBox->currentText()); QMessageBox::information(this, "提交成功", msg); }); } };

QMessageBox 对话框

C++
// 信息对话框
QMessageBox::information(this, "标题", "操作成功!");

// 警告对话框
QMessageBox::warning(this, "警告", "输入无效!");

// 错误对话框
QMessageBox::critical(this, "错误", "文件打开失败!");

// 确认对话框
auto reply = QMessageBox::question(this, "确认", "确定要删除吗?",
    QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
    // 执行删除
}

编程练习

创建一个用户注册界面,包含以下控件并排列整齐:用户名输入框(QLineEdit)、密码输入框(QLineEdit,设置为密码模式)、性别选择(QRadioButton 男/女)、兴趣爱好(QCheckBox 音乐/运动/阅读)、提交按钮(QPushButton)。点击提交时,在控制台输出所有输入内容。

布局管理

通俗类比

布局就像房间家具的摆放规划。QHBoxLayout 是一字排开(客厅沙发沿墙),QVBoxLayout 是上下叠放(衣柜里的抽屉层叠),QGridLayout 是网格分布(棋盘上的格子)。好的布局让家具无论房间大小变化,都能自动调整位置。

Qt 使用布局(Layout)来自动管理控件的位置和大小。当窗口大小改变时,布局会自动调整控件的位置和尺寸。

常用布局类

布局类排列方向特点
QHBoxLayout水平控件从左到右排列
QVBoxLayout垂直控件从上到下排列
QGridLayout网格按行和列排列
QFormLayout表单标签-字段成对排列

布局示例

C++
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>

class LayoutDemo : public QWidget {
public:
    LayoutDemo(QWidget *parent = nullptr) : QWidget(parent) {
        // ===== 水平布局 =====
        QHBoxLayout *hLayout = new QHBoxLayout();
        hLayout->addWidget(new QPushButton("按钮1"));
        hLayout->addWidget(new QPushButton("按钮2"));
        hLayout->addWidget(new QPushButton("按钮3"));
        // 添加弹性空间
        hLayout->addStretch();
        
        // ===== 网格布局 =====
        QGridLayout *gLayout = new QGridLayout();
        gLayout->addWidget(new QLabel("姓名:"), 0, 0);   // 第0行第0列
        gLayout->addWidget(new QLineEdit(), 0, 1);      // 第0行第1列
        gLayout->addWidget(new QLabel("年龄:"), 1, 0);   // 第1行第0列
        gLayout->addWidget(new QLineEdit(), 1, 1);      // 第1行第1列
        // 跨列
        gLayout->addWidget(new QPushButton("提交"), 2, 0, 1, 2);
        
        // ===== 主布局(垂直)=====
        QVBoxLayout *mainLayout = new QVBoxLayout(this);
        mainLayout->addLayout(hLayout);
        mainLayout->addLayout(gLayout);
        mainLayout->addStretch();  // 底部弹性空间
    }
};
提示

布局可以嵌套:将水平布局放入垂直布局,或将多个布局组合成复杂的界面。使用 addStretch() 可以填充空白区域,使控件对齐到一边。

编程练习

使用布局管理器重新实现上题的用户注册界面,要求:使用 QFormLayout 排列标签和输入框,使用 QHBoxLayout 排列性别选项,使用 QVBoxLayout 作为顶层布局。调整窗口大小时,所有控件应自动适应。

对话框

通俗类比

对话框就像弹出式通知。消息框(QMessageBox)是门口的告示"今日休息";文件对话框是"请把文件放到这个托盘里";输入对话框是"请在这里签个名"。它们短暂出现,完成任务后就消失。

对话框是一种特殊的窗口,用于与用户进行短暂的交互。Qt 提供了多种内置对话框类。

内置对话框

C++
#include <QFileDialog>
#include <QColorDialog>
#include <QFontDialog>
#include <QInputDialog>
#include <QMessageBox>

// 文件对话框
QString fileName = QFileDialog::getOpenFileName(this,
    "选择文件", "C:/", "文本文件 (*.txt);;所有文件 (*.*)");

// 保存文件对话框
QString saveFile = QFileDialog::getSaveFileName(this,
    "保存文件", "untitled.txt", "文本文件 (*.txt)");

// 颜色对话框
QColor color = QColorDialog::getColor(Qt::red, this, "选择颜色");
if (color.isValid()) {
    label->setStyleSheet(QString("color: %1").arg(color.name()));
}

// 字体对话框
bool ok;
QFont font = QFontDialog::getFont(&ok, QFont("Arial", 12), this);
if (ok) {
    textEdit->setFont(font);
}

// 输入对话框
QString text = QInputDialog::getText(this, "输入", "请输入名字:");
int num = QInputDialog::getInt(this, "输入", "请输入年龄:", 18, 0, 150);

自定义对话框

C++
#include <QDialog>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>

class AboutDialog : public QDialog {
public:
    AboutDialog(QWidget *parent = nullptr) : QDialog(parent) {
        setWindowTitle("关于");
        setFixedSize(300, 200);
        
        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(new QLabel("

我的应用

"
)); layout->addWidget(new QLabel("
版本 1.0
"
)); QPushButton *okBtn = new QPushButton("确定"); connect(okBtn, &QPushButton::clicked, this, &QDialog::accept); layout->addWidget(okBtn); } }; // 使用 AboutDialog dlg(this); if (dlg.exec() == QDialog::Accepted) { qDebug() << "用户点击了确定"; }

编程练习

在程序中添加以下功能:点击按钮时弹出 QFileDialog 选择文本文件,读取文件内容并显示在 QTextEdit 中。若文件无法打开,使用 QMessageBox::warning 提示错误。

事件处理

通俗类比

事件处理就像餐厅服务员的工作。鼠标点击是"客人招手",键盘按下是"客人喊话",定时器是"每5分钟巡桌一次"。服务员(事件处理函数)要响应各种需求,让客人满意(用户体验好)。

Qt 使用事件(Event)来处理用户输入和系统消息。当用户点击鼠标、按下键盘或窗口需要重绘时,Qt 会产生相应的事件并发送给对应的控件。

常见事件类型

事件类触发时机常用方法
QMouseEvent鼠标按下/移动/释放pos(), button()
QKeyEvent键盘按下/释放key(), text()
QPaintEvent需要重绘窗口rect()
QResizeEvent窗口大小改变size(), oldSize()
QTimerEvent定时器超时timerId()
QCloseEvent窗口关闭时accept(), ignore()

重写事件处理函数

C++
#include <QWidget>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPainter>
#include <qDebug>

class EventWidget : public QWidget {
public:
    EventWidget(QWidget *parent = nullptr) : QWidget(parent) {
        setMouseTracking(true);  // 开启鼠标追踪
    }
    
protected:
    // 鼠标按下事件
    void mousePressEvent(QMouseEvent *event) override {
        if (event->button() == Qt::LeftButton) {
            qDebug() << "鼠标左键按下:" << event->pos();
        }
    }
    
    // 鼠标移动事件
    void mouseMoveEvent(QMouseEvent *event) override {
        qDebug() << "鼠标位置:" << event->pos();
    }
    
    // 键盘事件
    void keyPressEvent(QKeyEvent *event) override {
        switch (event->key()) {
            case Qt::Key_Up:    qDebug() << "上箭头"; break;
            case Qt::Key_Down:  qDebug() << "下箭头"; break;
            case Qt::Key_Left:  qDebug() << "左箭头"; break;
            case Qt::Key_Right: qDebug() << "右箭头"; break;
            case Qt::Key_Escape: close(); break;
        }
    }
    
    // 绘制事件
    void paintEvent(QPaintEvent *) override {
        QPainter painter(this);
        painter.setPen(Qt::red);
        painter.drawText(rect(), Qt::AlignCenter, "按方向键或点击鼠标");
    }
};

定时器

C++
#include <QTimer>

// 方式1:QTimer 类
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [=]() {
    qDebug() << "定时器触发!";
});
timer->start(1000);  // 每 1000 毫秒(1秒)触发一次

// 方式2:单次定时器
QTimer::singleShot(5000, [=]() {
    qDebug() << "5秒后执行";
});

编程练习

创建一个自定义 QWidget 子类,重写鼠标事件和键盘事件:在窗口中移动鼠标时,在状态栏显示当前坐标;按下方向键时,在窗口中移动一个 QLabel 的位置(每次移动 10 像素)。

绘图系统

通俗类比

绘图就像在画布上作画。QPainter 是你的画笔和调色盘,QPen 是勾线笔(轮廓),QBrush 是填充颜料(内部颜色)。你可以画直线、矩形、圆形、甚至导入图片(贴贴纸)。

Qt 的绘图系统基于 QPainter 类,提供了绘制基本形状、文本、图片和矢量图形的功能。所有的绘制操作都在 paintEvent() 中进行。

基本绘图

C++
#include <QPainter>
#include <QPaintEvent>

void paintEvent(QPaintEvent *) override {
    QPainter painter(this);
    
    // 设置画笔(线条颜色)
    painter.setPen(QPen(Qt::blue, 2));
    
    // 设置画刷(填充颜色)
    painter.setBrush(QBrush(Qt::yellow));
    
    // 绘制基本形状
    painter.drawLine(10, 10, 100, 10);           // 直线
    painter.drawRect(10, 30, 80, 60);           // 矩形
    painter.drawEllipse(120, 30, 80, 60);     // 椭圆
    painter.drawArc(10, 110, 80, 80, 0, 5760);  // 圆弧
    
    // 绘制文本
    painter.setPen(Qt::black);
    painter.setFont(QFont("Arial", 16));
    painter.drawText(120, 120, "Hello Qt!");
    
    // 绘制圆角矩形
    painter.drawRoundedRect(120, 150, 100, 50, 10, 10);
}

绘制图片

C++
// 加载并绘制图片
QPixmap pixmap(":/images/logo.png");  // 从资源文件加载
painter.drawPixmap(10, 10, pixmap);     // 在指定位置绘制

// 缩放绘制
painter.drawPixmap(10, 10, 100, 100, pixmap);

// 从文件加载
QImage image("C:/pics/photo.jpg");
painter.drawImage(0, 0, image);
说明

QPainter 支持抗锯齿(setRenderHint(QPainter::Antialiasing))、坐标变换(平移、旋转、缩放)、裁剪和渐变填充。Qt 的资源系统(.qrc 文件)可以将图片等资源编译到可执行文件中。

编程练习

创建一个自定义绘制控件,重写 paintEvent 函数,绘制以下内容:

  1. 一个填充为蓝色、边框为黑色的矩形(200x100)
  2. 一个红色填充的圆形(半径 50)
  3. 一条从左上角到右下角的对角线
  4. 在坐标 (100, 50) 处绘制文本"Qt Painting"

Qt Designer

通俗类比

Qt Designer 就像室内设计软件的拖拽版。不用写代码画墙、放家具,你只需要从素材库(控件面板)里拖出沙发、桌子,摆放到房间(窗口)里,双击设置颜色尺寸——所见即所得,比手写代码快得多。

Qt Designer 是 Qt 提供的可视化界面设计工具。通过拖拽控件来设计界面,无需手动编写布局代码,大大提高开发效率。

使用 Qt Designer

  1. 在 Qt Creator 中,双击 mainwindow.ui 文件打开 Qt Designer
  2. 从左侧面板拖拽控件到窗口上
  3. 使用布局工具栏(上方按钮)对控件进行布局
  4. 在右侧面板设置控件的属性(名称、文本、大小等)
  5. 按 Ctrl+R 预览界面效果

UI 文件与代码的关联

Qt Creator 会自动生成 ui 头文件。在 C++ 代码中访问 UI 控件:

C++
// mainwindow.h
class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    
private slots:
    void on_submitBtn_clicked();  // 自动连接
    
private:
    Ui::MainWindow *ui;  // UI 指针
};

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);  // 初始化 UI
    
    // 访问 UI 控件
    ui->nameEdit->setPlaceholderText("请输入姓名");
    ui->titleLabel->setText("欢迎");
    
    // 手动连接信号槽
    connect(ui->submitBtn, &QPushButton::clicked, this, &MainWindow::on_submitBtn_clicked);
}

void MainWindow::on_submitBtn_clicked() {
    QString name = ui->nameEdit->text();
    ui->resultLabel->setText("你好, " + name);
}
提示

Qt 的信号槽支持自动连接:如果槽函数名遵循 on_控件名_信号名 的命名规则,setupUi() 会自动连接信号和槽。例如 on_submitBtn_clicked 会自动连接 submitBtnclicked 信号。

编程练习

使用 Qt Designer 设计一个计算器界面:包含两个数字输入框、一个运算符选择下拉框(+、-、*、/)和一个计算结果按钮。将 .ui 文件加载到程序中,编写代码实现计算功能并显示结果。

综合项目:简易计算器

通俗类比

做一个完整的 Qt 项目就像策划一场生日派对。你需要设计邀请函(界面布局)、安排节目流程(信号槽连接)、准备背景音乐(资源文件)、布置场地(样式美化)。每个部分都要协调配合,派对才能成功举办。

让我们综合运用所学知识,开发一个功能完整的计算器应用程序。

项目结构

C++ (Calculator.h)
#ifndef CALCULATOR_H
#define CALCULATOR_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class Calculator; }
QT_END_NAMESPACE

class Calculator : public QMainWindow {
    Q_OBJECT
public:
    Calculator(QWidget *parent = nullptr);
    ~Calculator();
    
private slots:
    void onDigitClicked();       // 数字按钮
    void onOperatorClicked();    // 运算符按钮
    void onEqualClicked();       // 等于按钮
    void onClearClicked();       // 清除按钮
    
private:
    Ui::Calculator *ui;
    double m_firstNum = 0;
    double m_secondNum = 0;
    QString m_operator;
    bool m_waitingForSecond = false;
};

#endif
C++ (Calculator.cpp)
#include "Calculator.h"
#include "ui_Calculator.h"

Calculator::Calculator(QWidget *parent) : QMainWindow(parent), ui(new Ui::Calculator) {
    ui->setupUi(this);
    setWindowTitle("计算器");
    
    // 连接数字按钮(0-9)
    connect(ui->btn0, &QPushButton::clicked, this, &Calculator::onDigitClicked);
    connect(ui->btn1, &QPushButton::clicked, this, &Calculator::onDigitClicked);
    // ... 连接其他数字按钮
    
    // 连接运算符
    connect(ui->btnPlus, &QPushButton::clicked, this, &Calculator::onOperatorClicked);
    connect(ui->btnMinus, &QPushButton::clicked, this, &Calculator::onOperatorClicked);
    connect(ui->btnMul, &QPushButton::clicked, this, &Calculator::onOperatorClicked);
    connect(ui->btnDiv, &QPushButton::clicked, this, &Calculator::onOperatorClicked);
    
    // 连接功能按钮
    connect(ui->btnEqual, &QPushButton::clicked, this, &Calculator::onEqualClicked);
    connect(ui->btnClear, &QPushButton::clicked, this, &Calculator::onClearClicked);
}

void Calculator::onDigitClicked() {
    QPushButton *btn = qobject_cast<QPushButton*>(sender());
    if (!btn) return;
    
    QString digit = btn->text();
    if (m_waitingForSecond) {
        ui->display->setText(digit);
        m_waitingForSecond = false;
    } else {
        QString current = ui->display->text();
        ui->display->setText(current == "0" ? digit : current + digit);
    }
}

void Calculator::onOperatorClicked() {
    m_firstNum = ui->display->text().toDouble();
    QPushButton *btn = qobject_cast<QPushButton*>(sender());
    m_operator = btn->text();
    m_waitingForSecond = true;
}

void Calculator::onEqualClicked() {
    m_secondNum = ui->display->text().toDouble();
    double result = 0;
    
    if (m_operator == "+") result = m_firstNum + m_secondNum;
    else if (m_operator == "-") result = m_firstNum - m_secondNum;
    else if (m_operator == "×") result = m_firstNum * m_secondNum;
    else if (m_operator == "÷") {
        if (m_secondNum == 0) { ui->display->setText("Error"); return; }
        result = m_firstNum / m_secondNum;
    }
    
    ui->display->setText(QString::number(result));
}

void Calculator::onClearClicked() {
    ui->display->setText("0");
    m_firstNum = m_secondNum = 0;
    m_operator.clear();
    m_waitingForSecond = false;
}

图形界面学习完成!

接下来可以学习 cs/.NET/Unity应用开发,使用 SFML 库开发 2D 游戏。

编程练习

综合应用所学知识,设计并开发一个简易记事本应用,功能包括:

  1. 新建、打开、保存文本文件
  2. 文本编辑区域(QTextEdit)
  3. 字体设置(QFontDialog)
  4. 颜色设置(QColorDialog)
  5. 状态栏显示当前行号和列号

课后练习与项目实践

基础练习

作业 1:简易计算器界面

使用 Qt 创建一个窗口,包含两个 QLineEdit(输入数字)、一个 QComboBox(选择运算符 + - * /)、一个 QPushButton("计算"),以及一个 QLabel 显示结果。点击按钮时计算并显示结果。

参考实现思路

C++
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QComboBox>
#include <QPushButton>
#include <QLabel>
using namespace std;

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QWidget window;
    window.setWindowTitle("简易计算器");

    QVBoxLayout *mainLayout = new QVBoxLayout(&window);
    QHBoxLayout *inputLayout = new QHBoxLayout;

    QLineEdit *num1 = new QLineEdit;
    QLineEdit *num2 = new QLineEdit;
    QComboBox *op = new QComboBox;
    op->addItems({"+", "-", "*", "/"});
    QLabel *result = new QLabel("结果: ?");
    QPushButton *btn = new QPushButton("计算");

    inputLayout->addWidget(num1);
    inputLayout->addWidget(op);
    inputLayout->addWidget(num2);

    mainLayout->addLayout(inputLayout);
    mainLayout->addWidget(btn);
    mainLayout->addWidget(result);

    QObject::connect(btn, &QPushButton::clicked, [&]() {
        double a = num1->text().toDouble();
        double b = num2->text().toDouble();
        double r = 0;
        QString oper = op->currentText();
        if (oper == "+") r = a + b;
        else if (oper == "-") r = a - b;
        else if (oper == "*") r = a * b;
        else if (oper == "/") r = (b != 0) ? a / b : 0;
        result->setText("结果: " + QString::number(r));
    });

    window.show();
    return app.exec();
}

作业 2:猜数字游戏 GUI 版

把之前命令行的猜数字游戏改造成 Qt 界面版:显示提示("太大了"/"太小了"/"猜对了"),输入框猜数字,按钮提交,显示猜测次数。

参考实现思路

C++
// 核心逻辑:生成随机数后,在按钮点击时读取输入框
// 比较并更新提示标签和计数
// 猜对后弹出 QMessageBox::information 恭喜

// 伪代码:
int secret = rand() % 100 + 1;
int count = 0;

// 按钮槽函数:
void onGuess() {
    int guess = input->text().toInt();
    count++;
    if (guess > secret) hint->setText("太大了!");
    else if (guess < secret) hint->setText("太小了!");
    else {
        hint->setText("猜对了!");
        QMessageBox::information(this, "恭喜",
            "你猜了" + QString::number(count) + "次!");
    }
    countLabel->setText("次数: " + QString::number(count));
}

综合项目:番茄钟计时器

项目目标

制作一个番茄工作法计时器:25分钟倒计时,可以开始/暂停/重置。要求使用 QTimer、QLCDNumber(或 QLabel)显示剩余时间,QPushButton 控制,时间到弹出提示。

参考实现

C++
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QMessageBox>
#include <QTime>

class Pomodoro : public QWidget {
    Q_OBJECT
public:
    Pomodoro(QWidget *parent = nullptr) : QWidget(parent), remaining(25 * 60) {
        QVBoxLayout *layout = new QVBoxLayout(this);
        timeLabel = new QLabel("25:00", this);
        timeLabel->setStyleSheet("font-size:48px; color:#333;");
        timeLabel->setAlignment(Qt::AlignCenter);

        QPushButton *startBtn = new QPushButton("开始", this);
        QPushButton *resetBtn = new QPushButton("重置", this);

        layout->addWidget(timeLabel);
        layout->addWidget(startBtn);
        layout->addWidget(resetBtn);

        timer = new QTimer(this);
        timer->setInterval(1000);
        connect(timer, &QTimer::timeout, this, &Pomodoro::updateTime);
        connect(startBtn, &QPushButton::clicked, [this, startBtn]() {
            if (timer->isActive()) { timer->stop(); startBtn->setText("继续"); }
            else { timer->start(); startBtn->setText("暂停"); }
        });
        connect(resetBtn, &QPushButton::clicked, [this, startBtn]() {
            timer->stop();
            remaining = 25 * 60;
            updateDisplay();
            startBtn->setText("开始");
        });
        updateDisplay();
    }

private slots:
    void updateTime() {
        if (--remaining <= 0) {
            timer->stop();
            QMessageBox::information(this, "时间到", "休息一下吧!");
        }
        updateDisplay();
    }
    void updateDisplay() {
        int m = remaining / 60, s = remaining % 60;
        timeLabel->setText(QString("%1:%2").arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0')));
    }
private:
    QTimer *timer;
    QLabel *timeLabel;
    int remaining;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    Pomodoro w;
    w.setWindowTitle("番茄钟");
    w.resize(300, 200);
    w.show();
    return app.exec();
}

注:需在 .pro 文件中添加 QT += widgets,且使用 moc 编译 Q_OBJECT 类。