1. 构建系统
  2. 最小系统
  3. 信号和槽机制
  4. Qt 对象系统
  5. Qt 窗口体系
  6. 消息/事件机制
  7. 绘图和绘图设备
  8. 文件系统
  9. Socket 通信
  10. 多线程
  11. 数据库操作
  12. Qt 程序打包

概述

仅记录基本概念
细节可以在文档中搜索文中出现的各种类慢慢看

参考资料:

  • 传智播客的 Qt5 教程
  • Qt 文档

同时 Qt 可以结合 VS 和 vcpkg 系统一块儿使用,简直完美

1. 构建系统

Qt 的以qmake为构建系统,其配置文件为xxx.pro,相当于CMakeCmakeLists.txt
其基本语法为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 单行注释

# 设置一个配置/在已有配置上添加
CONFIG_NAME = XXX
CONFIG_NAME += YYY

# makefile模板类型(生成那种类型的makefile)
TEMPLATE = app
# app -建立一个应用程序的 makefile
# lib - 建立一个库的 makefile
# vcapp - 建立一个应用程序的 VisualStudio 项目文件
# vclib - 建立一个库的 VisualStudio 项目文件
# subdirs - 建立一个子模块

#指定生成的应用程序名:
TARGET = QtDemo

#工程中包含的头文件和源代码
HEADERS += include/painter.h
SOURCES += sources/main.cpp sources/painter.cpp

#工程中包含的ui设计文件和资源文件
FORMS += forms/painter.ui
RESOURCES += qrc/painter.qrc

#添加链接模块
QT += widgets

#添加qmake配置信息
CONFIG += c++11

#条件语句
greaterThan(QT_MAJOR_VERSION, 4):QT += widgets

2. 最小程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
// QApplication 应用程序类,负责
// 1. 含主事件循环
// 2. 事件处理和调度
// 3. 应用程序的初始化和结束
// 4. 对话管理
QApplication a(argc, argv);

// 应用程序的控件
QWidget w; w.show();

// 进入消息循环
return a.exec();
}

3. 信号和槽机制

3.1 基本概念

  • 信号

    信号指某个控件在接收到某个事件后自行发出的一个通知信号
    这个信号不指向任何目标,仅仅只是一个信号
    对这个信号感兴趣的控件可以通过连接函数把这个信号和其他处理函数进行连接

  • 通过连接函数与信号连接的特殊函数就是槽函数
    在信号触发的时候,槽函数会被通过预设的方式调用

3.2 使用方法

槽函数的参数和顺序必须和信号的参数保持一致
允许槽函数的参数少于信号的参数

  1. 使用函数指针的方式连接

    1
    2
    3
    4
    5
    // sender:发出信号的对象
    // signal:发送对象发出的信号
    // receiver:接收信号的对象
    // slot:接收对象在接收到信号之后所需要调用的函数
    connect(sender, signal, receiver, slot);
  2. 使用字符串的方式连接

    1
    2
    3
    // 其中SIGNAL和SLOT是两个宏,会把函数转换成字符串
    // 同时这种方式不会产生编译错误,只会产生运行期错误...
    connect(button, SIGNAL(clicked()), &a, SLOT(quit()));

    3.3 自定义信号槽

首先为了自定义信号槽,必须继承QObject
然后在类的第一行使用Q_OBJECT宏(这个宏提供信号槽机制,国际化机制,以及 Qt 特有的反射能力)

信号

Qt 增加了signal块,其中的函数即是信号函数
信号函数返回值必须为void,同时也不需要提供具体实现

emit 是一个 Qt 扩展的关键字,后面接调用信号函数的语句即可触发信号

槽函数

Qt5 中任何函数(成员函数,static 函数,全局函数和 Lambda 表达式)都可以作为槽函数

  • 一个信号可以连接多个槽,但是槽被调用的顺序不确定
  • 多个信号可以连接一个槽,任何一个信号发出都会调用槽函数
  • 信号可以连接其他信号,即触发其他信号
  • 槽可以被取消连接,当一个对象 delete 后,Qt 自动取消连接到这个对象上的槽
  • 如果信号发射者和接收者不在同一个线程中,则在信号发出后,再次轮到接收者线程执行时才执行槽函数

4. Qt 对象体系

Qt 中的对象

Qt 中的根对象为QObject
而所有窗口及窗口控件的根对象为QWiget(也继承自 QObject)

Qt 中的内存管理

在 Qt 中,不同的对象以树的形式组织起来
一个对象在创建时可以指定一个父对象指针(也可以后面再设置)

对象会自动添加到父对象的children列表
父对象析构的时候会自动析构其所有子对象

当对象在堆上创建时,Qt 中的对象树可以很可靠地把对象析构
当对象在栈上创建时,需要遵守一些规定,以保证对象不会被作用域结束和对象树析构两次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
// 父对象在前,子对象在后
// 作用域结束后会先析构子对象
// 同时会子对象自动从父对象的子对象列表中删除
// 在析构父对象的时候不会重复析构子对象
QWidget window;
QPushButton quit("Quit", &window);
}

{
// 但是如果子对象在父对象前面
// 子对象就会依次被父对象和作用域析构两次
// 这个不会引发编译错误,但会引发运行错误...
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}

5. Qt 窗口体系

首先 Qt 中的坐标以左上角为原点,横向为 x 坐标,纵向为 y 坐标,向右向下正向
同时窗口坐标以控件父组件坐标为基准

5.1 QMainWindows

QMainWindow 类是 Qt 中的主窗口类
提供一个菜单栏(menu bar), 多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar) 及一个中心部件(central widget)

5.2 资源文件

Qt 内部资源文件可以以二进制的方式编译到可执行文件中
可以在 Qt 内部给文件起别名,通过别名来使用文件

5.3 控件

Qt 中的控件派生自QWidget

内置控件可以参考 Qt Designer 中各种控件的使用方法(Qt 本身文档有介绍)
或者参考 Zeal 中的 Qt 文档

5.4 对话框

对话框常用来进行一些交互任务(如浏览资源管理器并打开文件),QDialog为 Qt 中的对话框基类

对话框对父对象有不同的使用方法:
指定父对象时,对话框为父组件的子对话框,在任务栏共享父对象的位置
不指定父对象,为顶层窗口,在任务栏占用你单独的位置

同时对话框有三种类型:

应用程序级别的模态对话框:
完成对话前阻塞同一程序其他窗口的输入(必须优先完成此对话框)
使用QDialog::exec()执行

窗口级别的模态对话框:
阻塞与窗口关联的输入操作,允许与其他窗口互动
使用QDialog::open()执行

非模态对话框:
可以切换到其他窗口进行操作
使用QDialog::show()执行

Qt 内置了一系列通用的对话框

对话框 作用
QColorDialog 选择颜色
QFileDialog 选择文件/目录
QFontDialog 选择字体
QInputDialog 获取输入
QMessageBox 显示消息
QPageSetupDialog 打印纸张操作
QPrintDialog 打印机操作
QPrintPreviewDialog 打印预览
QProgressDialog 显示操作

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 应用程序模态窗口
void MainWindow::open() {
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!")); dialog.exec();
}

// 非模态窗口
// 非模态窗口的调用不会阻塞
// 所以如果在栈上创建对话框对象会被立即析构
// 如果依赖对象树析构,则父对象不析构前对话框对象也不会析构
// 通过设置Qt::WA_DeleteOnClose属性可以让对话框在退出时自动析构
void MainWindow::open() {
QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
}

5.5 布局管理器

Qt 提供两种布局方式:绝对定位布局,布局管理器布局

布局管理器 作用
QHBoxLayout 从左到右布局
QVBoxLayout 从上到下布局
QGridLayout 网格布局

6. 消息/事件机制

Qt 中的组件基类QWidget有一系列虚函数用于作为事件处理函数
但事件传递给组件后,会由event()函数处理,并分发给不同的事件处理函数

event()函数负责进行事件的分发,如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false
如果返回值是 true,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件
在 event()函数中,调用事件对象的 accept()和 ignore()函数是没有作用的,不会影响到事件的传播

  • keyPressEvent()
  • keyReleaseEvent()
  • mouseDoubleClickEvent()
  • mouseMoveEvent()
  • mousePressEvent()
  • mouseReleaseEvent()

如果想要屏蔽特定某个事件,可以重写event()函数(这样有点复杂),也可以使用事件过滤器
事件过滤器会在event()函数处理之前过滤所有事件,通过返回true指定事件已处理,不需要继续传播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 函数原型
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );

bool MainWindow::eventFilter(QObject *obj, QEvent *event) {

// 我们定义的类在textEdit控件上设置一个过滤器
if (obj == textEdit) {

if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}

} else {

// 处理父类的过滤器
return QMainWindow::eventFilter(obj, event);

}
}

// 安装过滤器的函数原型(任何QObject都有事件过滤器,只是有的没有重写)
// 最后安装的过滤器会最先被执行
void QObject::installEventFilter ( QObject * filterObj );

// 卸载过滤器
QObject::removeEventFilter();

// 事件过滤器和被安装过滤器的组件必须在同一线程
// 否则过滤器将不起作用
// 另外,如果在安装过滤器之后,这两个组件到了不同的线程,只有等到二者重新回到同一线程的时候过滤器才会有效

7. 绘图和绘图设备

Qt 绘图的过程:
使用QPainterQPaintDevice上进行绘制
QPainter发出指令,经过QPaintEngine进行传递,由QPaintDevice进行转换和显示
如果不编写自定义的QPaintDevice的话,无需关心QPaintEngine

Qt 内有 4 个常用的绘图设备

绘图设备 用途
QPixmap 针对屏幕显示进行了优化,与操作系统相关,在不同操作系统上效果不同
QBitmap QPixmap 的子类,特化为只有黑白两色,性能较好,可用于特殊场景使用
QImage 专用于图片 IO 等操作,可对图片进行像素级访问
QPicture 可以存储和重现各种 QPainter 的操作

8. 文件系统

Qt 提供了一个跨平台的文件操作系统

QIODevice:所有 I/O 设备类的父类,提供了字节块读写的通用操作以及基 本接口;  QFileDevice:Qt5 新增加的类,提供了有关文件操作的通用实现。  QFlie:访问本地文件或者嵌入资源;  QTemporaryFile:创建和访问本地文件系统的临时文件;  QBuffer:读写 QbyteArray, 内存文件;  QProcess:运行外部程序,处理进程间通讯;  QAbstractSocket:所有套接字类的父类;  QTcpSocket:TCP 协议网络数据传输;  QUdpSocket:传输 UDP 报文;  QSslSocket:使用 SSL/TLS 传输数据;

作用 属性
QIODevice 提供了字节块读写的通用操作及基本接口
QFileDevice 文件操作的通用操作实现
QFile 访问本地文件操作 随机访问
QTemporaryFile 本地临时文件操作 随机访问
QBuffer 读写 QbyteArray 内存文件 随机访问
QProcess 运行外部程序,处理进程间通信 顺序访问
QAbstractSocket 套接字类基类
QTcpSocket TCP 协议数据传输 顺序访问
QUdpSocket UDP 协议数据传输 顺序访问
QSslSocket SSL/TLS 传输数据

9. Socket 通信

Qt 中的 Socket 类都是非阻塞的

QTcpSocketQUdpSocket分别提供 TCP 和 UDP 通信
其中 UDP 可以提供局域网广播

详细内容可以参考这两个类的文档

10. 多线程

Qt 提供QThread类来实现多线程,继承并实现run()虚函数即可
外部通过start()来进行启动(类似 Java 中的多线程)

把我们的线程类中的finished()信号与deleteLater()函数连接,即可让线程完成时自动清理线程实例

还有一种多线程操作方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Worker : public QObject { Q
_OBJECT
private slots:
void onTimeout() {
qDebug()<<"Worker::onTimeout get called from?: "
<<QThread::currentThreadId();
}
};

int main(int argc, char *argv[]) {
QApplication a(argc, argv);
qDebug()<<"From main thread: "
<<QThread::currentThreadId();

QThread t;
QTimer timer;
Worker worker;

QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
// 启动定时器
timer.start(1000);
// 将类对象移交个线程
worker.moveToThread(&t);
// 启动线程 t.start();

return a.exec();
}

非主线程不可操纵 UI 对象,要被移动到子线程中处理的模块类在创建时不可以指定父对象
同时如果该模块类对象被析构,则信号槽连接自动断开

11. 数据库操作

Qt 提供一系列类进行数据库操作(仅限关系型数据库)
详见 Qt 自带文档

12. Qt 程序打包

单独编译出来的 Qt 可执行文件还需要一系列的动态链接库等才能工作
Qt 提供了windeployqt工具,可以根据编译得到的可执行文件自动找到所有依赖的包并拷贝到当前目录
详细内容可以参考 Qt 自带文档