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 功能:事件循环、定时器、数据结构 |
QtGui | GUI 基础:窗口系统集成、事件处理、2D 图形 |
QtWidgets | 桌面 GUI 控件:按钮、标签、文本框等 |
QtNetwork | 网络编程:TCP/UDP 通信、HTTP 请求 |
QtSql | 数据库访问:SQLite、MySQL 等 |
QtMultimedia | 多媒体:音频、视频、摄像头 |
编程练习
调研并对比三种 C++ GUI 框架(Qt、wxWidgets、FLTK)的特点,列出各自的优势和适用场景。说明为什么 Qt 在跨平台开发中占有重要地位。
Qt 安装
通俗类比
安装 Qt 就像组装一套高级厨具。你需要下载安装包(买厨具)、选择组件(挑选锅碗瓢盆)、配置环境变量(把厨具放在厨房顺手的位置)。装好后,你就可以开始做菜(开发程序)了。
方法一:Qt 在线安装器(推荐)
- 访问 qt.io/download 下载 Qt Online Installer
- 运行安装器,需要注册 Qt 账号(免费)
- 选择安装路径(建议不含中文和空格)
- 在组件选择页面,勾选以下内容:
- Qt 6.x.x → MinGW 或 MSVC(编译器)
- Qt 6.x.x → Qt 5 Compatibility Module
- Qt 6.x.x → Additional Libraries → Qt Charts, Qt Network
- Developer and Designer Tools → Qt Creator(IDE),CMake,MinGW
- 点击安装,等待下载完成
方法二:包管理器(Linux)
# 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 创建项目
- 打开 Qt Creator,点击 "新建项目"
- 选择 Application (Qt) → Qt Widgets Application
- 输入项目名称(如
FirstQtApp),选择保存路径 - 构建系统选择 CMake 或 qmake
- 类名保持默认
MainWindow,基类选择QMainWindow - 完成创建,按 Ctrl+R 运行项目
程序结构解析
#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_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 并指定父对象时,它会被自动添加到父对象的子对象列表中。当父对象被销毁时,所有子对象也会被自动销毁。
// 不需要手动 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):将信号和槽关联起来
连接信号与槽
#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() 函数详解
// 语法: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"; });
自定义信号与槽
// 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() |
控件使用示例
#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 对话框
// 信息对话框 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 | 表单 | 标签-字段成对排列 |
布局示例
#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 提供了多种内置对话框类。
内置对话框
#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);
自定义对话框
#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() |
重写事件处理函数
#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, "按方向键或点击鼠标"); } };
定时器
#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() 中进行。
基本绘图
#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); }
绘制图片
// 加载并绘制图片 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 函数,绘制以下内容:
- 一个填充为蓝色、边框为黑色的矩形(200x100)
- 一个红色填充的圆形(半径 50)
- 一条从左上角到右下角的对角线
- 在坐标 (100, 50) 处绘制文本"Qt Painting"
Qt Designer
通俗类比
Qt Designer 就像室内设计软件的拖拽版。不用写代码画墙、放家具,你只需要从素材库(控件面板)里拖出沙发、桌子,摆放到房间(窗口)里,双击设置颜色尺寸——所见即所得,比手写代码快得多。
Qt Designer 是 Qt 提供的可视化界面设计工具。通过拖拽控件来设计界面,无需手动编写布局代码,大大提高开发效率。
使用 Qt Designer
- 在 Qt Creator 中,双击
mainwindow.ui文件打开 Qt Designer - 从左侧面板拖拽控件到窗口上
- 使用布局工具栏(上方按钮)对控件进行布局
- 在右侧面板设置控件的属性(名称、文本、大小等)
- 按 Ctrl+R 预览界面效果
UI 文件与代码的关联
Qt Creator 会自动生成 ui 头文件。在 C++ 代码中访问 UI 控件:
// 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 会自动连接 submitBtn 的 clicked 信号。
编程练习
使用 Qt Designer 设计一个计算器界面:包含两个数字输入框、一个运算符选择下拉框(+、-、*、/)和一个计算结果按钮。将 .ui 文件加载到程序中,编写代码实现计算功能并显示结果。
综合项目:简易计算器
通俗类比
做一个完整的 Qt 项目就像策划一场生日派对。你需要设计邀请函(界面布局)、安排节目流程(信号槽连接)、准备背景音乐(资源文件)、布置场地(样式美化)。每个部分都要协调配合,派对才能成功举办。
让我们综合运用所学知识,开发一个功能完整的计算器应用程序。
项目结构
#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
#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 游戏。
编程练习
综合应用所学知识,设计并开发一个简易记事本应用,功能包括:
- 新建、打开、保存文本文件
- 文本编辑区域(QTextEdit)
- 字体设置(QFontDialog)
- 颜色设置(QColorDialog)
- 状态栏显示当前行号和列号
课后练习与项目实践
基础练习
作业 1:简易计算器界面
使用 Qt 创建一个窗口,包含两个 QLineEdit(输入数字)、一个 QComboBox(选择运算符 + - * /)、一个 QPushButton("计算"),以及一个 QLabel 显示结果。点击按钮时计算并显示结果。
参考实现思路
#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 界面版:显示提示("太大了"/"太小了"/"猜对了"),输入框猜数字,按钮提交,显示猜测次数。
参考实现思路
// 核心逻辑:生成随机数后,在按钮点击时读取输入框 // 比较并更新提示标签和计数 // 猜对后弹出 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 控制,时间到弹出提示。
参考实现
#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 类。