Qt5 的基础知识
个人博客:blogs.wurp.top
一. Qt 模块概述
Qt 采用模块化的设计,将功能划分为不同的模块。这带来了几个好处:减少应用程序大小(只链接需要的模块)、提高编译速度、清晰的功能划分以及便于维护。
Qt5 对模块进行了重构,核心模块更精简,许多功能移到了附加模块中。主要模块可以分为几类:
-
基础模块 (Essentials): 几乎所有 Qt 应用程序都需要。
- Qt Core: 提供核心的非 GUI 功能。包括:事件循环、信号与槽、对象模型、元对象系统、属性系统、线程、文件 I/O、容器类、变体类型、定时器、资源系统等。这是 Qt 的心脏。
- Qt GUI: 提供基础的 GUI 功能,不依赖于特定的窗口系统。包括:窗口系统集成、OpenGL 封装、2D 图形(如
QPainter
)、图像处理、字体、调色板、光标、基本窗口类(如QWindow
)。它为 Widgets 和 Quick 提供了底层支持。 - Qt Widgets: 提供基于传统像素和控件的 GUI。包含大量的预构建 UI 控件(按钮、文本框、列表、表格、对话框等)以及布局管理器。用于开发桌面风格的应用。建立在 Core 和 GUI 之上。
-
附加模块 (Add-ons): 提供特定领域的功能,按需使用。
- Qt Network: 提供 TCP/IP 网络编程支持(如
QTcpSocket
,QTcpServer
,QUdpSocket
)、HTTP/FTP 客户端(QNetworkAccessManager
)、DNS 查询等。 - Qt SQL: 提供数据库集成支持。统一的 API 访问 SQLite, MySQL, PostgreSQL, ODBC 等数据库。包含
QSqlDatabase
,QSqlQuery
,QSqlTableModel
等类。 - Qt Multimedia: 提供访问摄像头、音频和视频播放/录制的 API。包含
QMediaPlayer
,QCamera
,QAudioRecorder
等类。 - Qt Multimedia Widgets: 提供用于显示视频内容的小部件(如
QVideoWidget
,QCameraViewfinder
)。依赖于Qt Multimedia
和Qt Widgets
。 - Qt WebEngine (或 Qt WebKit - 已逐步淘汰): 提供集成现代 Web 浏览器引擎(基于 Chromium)的功能。
Qt WebEngineWidgets
提供基于 Widgets 的 Web 视图。 - Qt QML: 提供 QML 语言引擎和基础类型。用于声明式 UI 开发。
- Qt Quick: 建立在 QML 之上,提供用于构建流畅动画 UI 的类型和功能。包含
QQuickView
,QQuickItem
等核心类。用于现代 UI 开发。 - Qt Quick Controls 2: 提供基于 Qt Quick 的、高度风格化的现代 UI 控件集(按钮、滑块、列表视图等)。
- Qt Quick Dialogs: 提供标准的对话框(文件选择、颜色选择、消息框等)的 QML 类型。
- Qt Quick Layouts: 提供用于在 QML 中布局项目的类型(类似 Widgets 的布局管理器)。
- Qt Charts: 提供用于绘制各种图表(线图、柱状图、饼图等)的模块。
- Qt SerialPort: 提供访问串行端口(RS-232)的功能。
- Qt Bluetooth / Qt NFC: 提供蓝牙和近场通信功能。
- Qt Positioning: 提供位置信息访问(GPS 等)。
- Qt Sensors: 提供访问设备传感器(加速度计、陀螺仪等)的功能。
- Qt 3D: 提供高级 3D 功能。
- Qt Print Support: 提供打印支持(需要
Qt Widgets
)。 - Qt Help: 提供在线帮助系统支持。
- Qt Linguist Tools: 提供国际化(i18n)和本地化(l10n)工具链。
- Qt Network: 提供 TCP/IP 网络编程支持(如
-
工具模块:
- Qt Test: 提供 Qt 应用的单元测试框架。
-
如何使用模块:
在项目文件.pro
中,使用QT +=
来声明需要的模块。例如:
QT += core gui widgets network sql
在代码中,包含相应模块的头文件即可使用其功能。
二. Qt 的类结构与继承关系
Qt 拥有庞大但组织良好的类库。理解其核心继承关系至关重要:
-
根:
QObject
- 角色: 几乎所有 Qt 类的基类(特别是那些需要信号槽、对象树管理、元对象系统功能的类)。它提供:
- 对象树(父子关系)和内存管理(父对象销毁时自动删除子对象)。
- 信号与槽机制。
- 元对象系统(运行时类型信息 RTTI、动态属性、方法调用)。
- 事件处理的基础。
- 关键特性: 必须使用
Q_OBJECT
宏才能在子类中使用信号槽和元对象特性。
- 角色: 几乎所有 Qt 类的基类(特别是那些需要信号槽、对象树管理、元对象系统功能的类)。它提供:
-
核心继承分支:
QObject
QWidget
(属于Qt Widgets
模块)- 角色: 所有用户界面元素的基类(按钮、窗口、标签等)。它继承自
QObject
和QPaintDevice
。 - 功能: 处理用户输入(鼠标、键盘事件)、绘制自身、管理几何(位置、大小)、样式、焦点链、布局支持等。
- 子类: 几乎所有可视控件都继承自
QWidget
,例如QPushButton
,QLabel
,QLineEdit
,QMainWindow
,QDialog
,QFrame
,QComboBox
,QListView
,QTableView
等。
- 角色: 所有用户界面元素的基类(按钮、窗口、标签等)。它继承自
QWindow
(属于Qt GUI
模块)- 角色: 表示操作系统中的一个窗口。是更底层的窗口抽象。
QWidget
在桌面上通常内部使用QWindow
。 - 用途: 常用于 OpenGL 渲染、离屏渲染或需要直接操作底层窗口属性的场景。
- 角色: 表示操作系统中的一个窗口。是更底层的窗口抽象。
QCoreApplication
/QGuiApplication
/QApplication
- 角色: 应用程序类。每个 Qt 应用必须创建且仅创建一个这些类的实例。
QCoreApplication
: 用于非 GUI 应用(控制台应用),提供事件循环。QGuiApplication
: 用于基于QWindow
的 GUI 应用(不使用 Widgets 模块)。QApplication
: 用于基于QWidget
的传统 GUI 应用。继承自QGuiApplication
。- 功能: 管理事件循环、应用程序设置、命令行参数、系统范围的行为。
QThread
- 角色: 提供管理线程的方式。继承自
QObject
,但其对象通常“生活”在另一个线程中。
- 角色: 提供管理线程的方式。继承自
QAbstractItemModel
/QAbstractListModel
/QAbstractTableModel
- 角色: Model/View 架构中模型的基类。用于为视图(如
QListView
,QTableView
,QTreeView
)提供数据。
- 角色: Model/View 架构中模型的基类。用于为视图(如
QTimer
- 角色: 提供定时器功能,可以发出
timeout()
信号。
- 角色: 提供定时器功能,可以发出
QFile
,QTcpSocket
,QUdpSocket
,QProcess
…- 角色: 各种 I/O 和系统交互功能的类。
-
重要非
QObject
派生类:QString
: 功能强大的 Unicode 字符串处理类。QList
,QVector
,QMap
,QHash
,QSet
: STL 风格的容器类(通常比 STL 更方便,与 Qt API 集成更好)。QVariant
: 可以存储多种 Qt 和用户定义类型的“通用容器”。常用于属性系统、模型/视图、数据库操作等。QImage
,QPixmap
,QIcon
: 图像处理类。QPainter
: 用于在QPaintDevice
(如QWidget
,QImage
,QPixmap
)上执行 2D 绘制的类。QPoint
,QSize
,QRect
: 几何基本类型。
理解要点:
- 如果一个类需要信号槽、自动内存管理(通过父子关系)或动态属性,它必须直接或间接继承自
QObject
,并在类定义中使用Q_OBJECT
宏。 - GUI 控件几乎都继承自
QWidget
。 - 应用程序类 (
QApplication
/QGuiApplication
/QCoreApplication
) 是入口点并管理事件循环。 - 许多工具类(如
QString
,QList
)不继承自QObject
,因为它们不需要信号槽或父子关系管理。
三. 信号与槽机制 (Signals and Slots)
这是 Qt 最核心、最具革命性的特性,用于对象间的低耦合通信。它是对回调函数和观察者模式的强大替代。
-
概念:
- 信号 (Signal): 当一个对象的状态发生改变或者发生了某个事件时,它可以发出 (emit) 一个信号。信号本身是一个声明,没有实现(函数体)。信号通常由 Qt 自身(例如按钮的
clicked()
)或开发者定义。 - 槽 (Slot): 槽是一个普通的成员函数,可以被调用以响应某个信号的发出。它可以接收信号携带的参数。槽可以有实现,可以做任何操作(更新 UI、进行计算、触发其他信号等)。槽可以是
public slots
,protected slots
或private slots
(控制访问权限),也可以是普通的成员函数(Qt5 新语法)。 - 连接 (Connection): 使用
QObject::connect()
函数将一个对象的信号连接 (connect) 到另一个对象(或同一个对象)的槽。当信号发出时,Qt 的事件循环会确保连接到该信号的槽被调用。
- 信号 (Signal): 当一个对象的状态发生改变或者发生了某个事件时,它可以发出 (emit) 一个信号。信号本身是一个声明,没有实现(函数体)。信号通常由 Qt 自身(例如按钮的
-
语法 (Qt5 推荐 - 类型安全):
// 发送者指针, 发送者的信号(成员函数指针), 接收者指针, 接收者的槽(成员函数指针) connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName); // 示例:连接按钮的点击信号到窗口的关闭槽 connect(myButton, &QPushButton::clicked, myWindow, &QMainWindow::close); // 连接带参数的信号到槽 connect(spinBox, &QSpinBox::valueChanged, label, &QLabel::setNum);
-
旧语法 (Qt4, 仍支持但不推荐):
connect(sender, SIGNAL(signalName(type1, type2)), receiver, SLOT(slotName(type1, type2)));
-
关键特性与优势:
- 解耦: 发送者不知道接收者是谁,接收者也不知道发送者是谁。它们只需通过信号和槽的签名进行匹配。提高了模块化和可重用性。
- 类型安全 (Qt5 语法): 编译器可以检查信号和槽的参数类型是否兼容。
- 线程安全:
connect()
的第五个参数可以指定连接类型(如Qt::AutoConnection
,Qt::DirectConnection
,Qt::QueuedConnection
,Qt::BlockingQueuedConnection
),用于控制槽函数是在信号发出者的线程中执行(直接调用)还是在接收者的线程中执行(通过事件队列异步调用)。这对于跨线程通信至关重要。 - 灵活性: 一个信号可以连接多个槽,一个槽可以被多个信号连接。信号也可以连接到另一个信号(当第一个信号发出时,会立即触发第二个信号)。
- 自动断开: 当发送者或接收者对象被销毁时,Qt 会自动断开连接(避免了野指针调用)。
- 支持 Lambda 表达式 (Qt5): 可以直接将 Lambda 表达式作为槽连接到信号,非常方便:
connect(myButton, &QPushButton::clicked, [=]() {qDebug() << "Button clicked!";// 执行操作... });
-
注意事项:
- 信号和槽的参数数量、顺序、类型必须兼容(槽的参数个数可以少于信号的参数个数,多余的参数会被忽略)。
- 信号和槽的类(或其基类)必须包含
Q_OBJECT
宏,并且需要被moc
(元对象编译器)处理。 - 避免在槽中进行耗时操作,以免阻塞 GUI 线程(主事件循环)。耗时操作应放在单独的线程中。
四. Qt 的对象模型
Qt 在标准 C++ 对象模型的基础上进行了扩展,添加了几个关键特性,共同构成了 Qt 对象模型:
-
a. 对象树与所有权 (Parent-Child Relationship & Ownership):
- 当创建一个
QObject
时,可以指定一个父对象 (parent) 给它(通过构造函数传递QObject* parent
参数)。 - 父对象拥有其所有子对象。父对象被销毁时,会自动递归销毁其所有子对象。这极大地简化了内存管理,避免了内存泄漏。
- 可以使用
setParent()
改变对象的父对象。 - 使用
children()
获取子对象列表,使用findChild()
/findChildren()
按名称和类型查找子对象。 - 核心作用: 自动内存管理、逻辑分组(例如,一个窗口销毁时,其包含的所有按钮、标签等也自动销毁)。
- 当创建一个
-
b. 信号与槽 (Signals and Slots): (已在第 3 部分详细讲解)
- 作为对象模型的核心通信机制。
-
c. 元对象系统 (Meta-Object System):
- 目的: 提供 Qt 核心功能的运行时支持(信号槽、动态属性、RTTI、反射)。
- 核心组件:
Q_OBJECT
宏: 必须放在类定义的private
区域(通常在头文件中)。它声明了该类需要元对象特性,并触发moc
处理。- 元对象编译器 (
moc
): 一个 Qt 提供的预处理器。它读取包含Q_OBJECT
宏的头文件,生成一个额外的 C++ 源文件(通常是moc_*.cpp
)。这个文件包含:- 该类的元对象信息(类名、父类名、信号/槽签名、属性信息等)。
- 信号函数的实现(
moc
生成的代码会调用QMetaObject::activate
来真正发出信号)。 staticMetaObject
实例的初始化。
QMetaObject
类: 包含一个类的元信息(类名、信号、槽、属性、枚举、方法等)。每个使用Q_OBJECT
的类都有一个唯一的staticMetaObject
成员。QObject::metaObject()
: 返回指向对象所属类的QMetaObject
的指针。
- 提供的功能:
- 信号与槽:
moc
生成的代码是实现信号发射和槽查找/调用的关键。 - 运行时类型信息 (RTTI): 比标准 C++
typeid
更强大。使用qobject_cast()
进行安全的跨动态库边界的向下转型(类似dynamic_cast
,但不需要 RTTI 支持)。QObject *obj = ...; if (QPushButton *button = qobject_cast(obj)) {// 安全地使用 button }
- 动态属性: 可以在运行时给
QObject
实例添加和查询属性(setProperty("name", value)
,property("name")
),这些属性会被存储在QMetaObject
中。 - 反射 (Reflection): 在运行时检查类的结构(有哪些信号、槽、属性、方法等)并调用方法(
QMetaObject::invokeMethod()
)。 - 国际化 (i18n): 支持字符串翻译 (
tr()
)。
- 信号与槽:
-
d. 属性系统 (Property System):
- 允许在类中声明属性(通常在头文件中使用
Q_PROPERTY
宏)。 - 属性可以有类型、读函数(
READ
)、写函数(WRITE
)、通知信号(NOTIFY
)、设计器可见性、是否可编辑等特性。 - 用途:
- 设计器集成: Qt Designer 可以识别和编辑这些属性。
- QML 集成: QML 可以直接绑定和修改这些属性。
- 脚本: 可以通过脚本引擎访问。
- 数据绑定: 通知信号 (
NOTIFY
) 是实现属性变化自动更新 UI(在 QML 或自定义绑定中)的关键。 - 通用访问: 通过
property()
和setProperty()
访问(效率低于直接调用 READ/WRITE 函数)。
- 示例:
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
- 允许在类中声明属性(通常在头文件中使用
-
Qt 对象模型的核心价值: 通过对象树简化内存管理,通过信号槽实现优雅的通信,通过元对象系统提供强大的运行时能力和灵活性(动态属性、反射),为 Qt 的整个框架(尤其是 GUI 和 QML)奠定了基础。
五. Qt 的布局管理器 (Layout Managers)
在 GUI 开发中,手动设置每个控件的位置和大小 (setGeometry()
) 是繁琐且脆弱的(难以适应不同屏幕尺寸、DPI、字体大小、窗口缩放)。布局管理器解决了这个问题。
-
概念: 布局管理器 (
QLayout
的子类) 是负责自动排列和管理其内部控件(或其他布局)位置和大小的对象。它们根据布局策略、尺寸提示 (sizeHint()
,minimumSizeHint()
)、大小约束 (sizePolicy
) 以及可用空间来计算控件的最佳几何形状。 -
核心类:
QLayout
(基类)QBoxLayout
: 基类,管理单行/列控件。QHBoxLayout
: 水平排列控件。QVBoxLayout
: 垂直排列控件。
QGridLayout
: 网格布局。将控件放置在行和列组成的网格中。控件可以跨越多个行/列。QFormLayout
: 特别适合表单(标签-字段对)布局。通常产生两列网格。QStackedLayout
: 一次只显示一个控件(或子布局),其他控件被隐藏。常用于标签页或向导界面(常与QTabWidget
或QStackedWidget
配合使用)。
-
关键特性与使用:
- 添加控件: 使用
addWidget(QWidget *widget, ...)
将控件添加到布局中。QGridLayout
和QFormLayout
有更复杂的添加方法(指定行/列/跨度)。 - 添加布局: 使用
addLayout(QLayout *layout, ...)
将一个布局嵌套到另一个布局中。这是构建复杂界面的基础。 - 边距 (Margins):
setContentsMargins(int left, int top, int right, int bottom)
设置布局内容区域与容器边缘的距离。 - 间距 (Spacing):
setSpacing(int)
设置布局内控件之间的统一间距(QGridLayout
可以单独设置行/列间距setHorizontalSpacing()
/setVerticalSpacing()
)。 - 拉伸因子 (Stretch Factors): 使用
addStretch(int stretch = 0)
添加一个弹性空间(会占据剩余空间)。或者在使用addWidget()
/addLayout()
时指定拉伸因子参数。拉伸因子控制控件在可用空间中的相对大小比例。数值越大,分配到的空间越多。 - 尺寸策略 (
QSizePolicy
): 每个控件 (QWidget
) 都有一个尺寸策略 (sizePolicy()
),它告诉布局管理器该控件在水平和垂直方向上的行为偏好:- Fixed: 尺寸固定,不能被拉伸或收缩。只使用
sizeHint()
。 - Minimum:
sizeHint()
是最小尺寸,可以被拉伸。 - Maximum:
sizeHint()
是最大尺寸,可以被收缩。 - Preferred:
sizeHint()
是最佳尺寸,可以被拉伸或收缩。 - Expanding: 和
Preferred
类似,但更倾向于拉伸以填满可用空间。 - MinimumExpanding:
sizeHint()
是最小尺寸,更倾向于拉伸。 - Ignored: 忽略
sizeHint()
(非常少用)。
- Fixed: 尺寸固定,不能被拉伸或收缩。只使用
- 设置布局: 使用
QWidget::setLayout(QLayout *layout)
将一个布局应用到某个控件(通常是QFrame
,QGroupBox
或顶层窗口)上。这个控件成为布局的父对象和容器。 - 自动更新: 当父窗口大小改变、添加/移除控件、字体改变或调用
update()
/adjustSize()
时,布局会自动重新计算并调整所有子控件的大小和位置。
- 添加控件: 使用
-
使用布局管理器的优势:
- 自适应: UI 自动适应不同窗口大小、屏幕分辨率和 DPI 设置。
- 一致性: 确保控件间距和对齐方式一致。
- 易维护: 添加、移除或调整控件位置更容易,不需要手动计算坐标。
- 国际化友好: 轻松处理文本长度变化(不同语言翻译)。
- 简化开发: 无需硬编码几何信息。
-
设计建议:
- 组合使用多种布局(嵌套布局)来构建复杂界面。
- 充分利用拉伸因子分配空间。
- 理解并适当设置控件的尺寸策略。
- 使用
QSpacerItem
或addStretch()
创建弹性空间。 - 在 Qt Designer 中可视化地使用布局管理器是最高效的方式。
六. 总结:
掌握这五个核心方面(模块、类结构、信号槽、对象模型、布局)是高效使用 Qt5 进行 C++ 应用程序开发(尤其是 GUI 开发)的基础。理解它们如何协同工作,才能充分利用 Qt 提供的强大功能和开发效率。实践是巩固这些知识的最佳途径。建议从简单的 Widgets 应用开始,逐步尝试不同的模块和特性。