目录
目录
Posts List
  1. 什么是Qt
  2. 下载安装Qt
  3. 创建Qt项目
  4. QPushButton和第一个小程序
  5. 使用QDebug输出调试信息
  6. 对象树
  7. 信号和槽
  8. 自定义信号和槽
  9. 重载时的自定义信号和槽与信号连接信号
  10. Qt4及以前版本的信号槽连接

Qt学习 P1:什么是Qt,对象树,信号槽机制

学习资料:2019年最新QT从入门到实战完整版|传智播客

什么是Qt

Qt是一个跨平台的C++图形用户界面应用程序框架。

1991年,Qt最早由奇趣科技开发。

Qt的应用案例:

  1. Linux 桌面环境 KDE
  2. WPS Office 办公软件
  3. Skype 网络通话
  4. Google Earth 谷歌地图
  5. VLC 多媒体播放器
  6. ……

下载安装Qt

Qt按照不同版本发行,分为商业版和开源版。

可以从清华大学开源软件镜像站下载。

创建Qt项目

打开Qt Creator,点击New Project。

在New Project对话框中选择Application—Qt Widgets Application,填写项目名称,类信息基类改为QWidget,创建界面不勾选,完成。

新建的项目中自动生成了.pro文件,main.cpp,和widget.cpp/.h

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
01_FirstProject.pro

#-------------------------------------------------
#
# Project created by QtCreator 2020-06-17T11:43:06
#
#-------------------------------------------------

QT += core gui # 加载的模块,需要时在此手动添加

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = 01_FirstProject # 生成的程序名
TEMPLATE = app # 应用程序模板

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0


SOURCES += \ # 源文件
main.cpp \
widget.cpp

HEADERS += \ # 头文件
widget.h
1
2
3
4
5
6
7
8
9
10
11
12
13
main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv); // Qt应用程序类,有且仅有一个
Widget w;
w.show(); // 对象需要调用show方法才能显示

return a.exec(); // 程序进入消息循环
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Widget : public QWidget
{
Q_OBJECT // 一个宏定义

public:
Widget(QWidget *parent = 0); // 构造函数,有一个指向父亲的指针
~Widget();
};

#endif // WIDGET_H

使用新建向导还可以创建C++ Class等文件,.pro项目文件中会自动添加相关的文件名。

QPushButton和第一个小程序

头文件:

1
#include <QPushButton>

widget.cpp中Widget类的构造函数中创建新按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *btn = new QPushButton;
// btn->show(); // 直接调用show方法将在新窗口中显示按钮
btn->setParent(this); // 设置父亲为Widget窗口,按钮将依附于父亲的窗口中
btn->setText("Button1"); // 按钮文字

QPushButton *btn2 = new QPushButton("Button2", this); // 直接指定文字与父窗口
btn2->move(100, 100); // 设置按钮位置

resize(600, 400); // 调整Widget窗口大小
setFixedSize(600, 400); // 固定窗口大小

setWindowTitle("First Window"); // 设置窗口标题
}

Qt的窗口坐标:

左上角为原点(0, 0),向右X坐标增加,向下Y坐标增加。

使用QDebug输出调试信息

头文件:

1
#include <QDebug>

输出调试信息:

1
qDebug() << "Debug!"; // 无需endl,自动换行

对象树

Qt中对象的终极基类是QObject类,它以对象树的形式组织。

当你创建QObject对象时,构造函数会接受一个QObject指针parent,父对象指针。创建的新对象会被自动添加到其父对象的children()列表。

父对象析构时,children()列表中的所有对象也会被析构。

Qt的对象树机制一定程度上自动解决了内存回收的问题。为了避免出现奇怪的错误,Qt鼓励在堆上直接创建对象,并在创建时就指定parent

信号和槽

信号槽是Qt的重要机制。

所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

connect()函数的最常见形式由四部分组成connect(sender, signal, receiver, slot),分别为信号的发送者、信号、信号的接收者、槽函数(即接收到信号后需要调用的函数)。

利用系统自带的信号与槽(查阅帮助文档),可以在Widget::Widget()中添加以下代码:

1
connect(btn2, &QPushButton::clicked, this, &Widget::close);

该行代码使得当按钮2被单击,窗口关闭。

自定义信号和槽

在项目下新建类,继承自QObject类(使用自动回收机制)

场景:当老师饿了,学生请老师吃饭。

sender:老师

signal:饿了

receiver:学生

slot:请老师吃饭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
teacher.h

#ifndef TEACHER_H
#define TEACHER_H

#include <QObject>

class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);

signals:
// 在此写入自定义信号,只需声明,无需实现
// 返回值为void,可以有参数,可以重载
void hungry();

public slots:
};

#endif // TEACHER_H
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
student.h

#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);

signals:

public slots:
// 早期Qt版本必须将槽函数写在此,高级版本可以写在public下或全局下
// 返回值为void,需要实现,可以有参数,可以重载
void treat();
};

#endif // STUDENT_H

-----
student.cpp

void Student::treat()
{
qDebug() << "请老师吃饭";
}
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
35
36
37
38
39
40
41
42
43
44
45
46
widget.h

#include <QWidget>
#include "teacher.h"
#include "student.h"

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0);
~Widget();

Teacher *te;
Student *st;

void ClassOver();
};

-----
widget.cpp

#include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
te = new Teacher(this);
st = new Student(this);

connect(te, &Teacher::hungry, st, &Student::treat); // 创建链接

ClassOver(); // 下课了
}

Widget::~Widget()
{

}


void Widget::ClassOver()
{
emit te->hungry(); // 触发信号
}

重载时的自定义信号和槽与信号连接信号

添加重载函数

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
teacher.h

void hungry();
void hungry(QString foodName);

-----
student.h

void treat();
void treat(QString foodName);

-----
student.cpp

void Student::treat(QString foodName)
{
qDebug() << "请老师吃" << foodName.toUtf8().data();
}

-----
widget.cpp

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
te = new Teacher(this);
st = new Student(this);

// connect(te, &Teacher::hungry, st, &Student::treat); // 创建链接
void (Teacher::*teacherSignal)(QString) = &Teacher::hungry;
void (Student::*studentSlot)(QString) = &Student::treat; // 注意变量名前需要加上类名

connect(te, teacherSignal, st, studentSlot); // 使用函数指针得到正确的函数地址

// ClassOver();

QPushButton *btn = new QPushButton("下课", this);
resize(600, 400);
connect(btn, &QPushButton::clicked, this, &Widget::ClassOver); // 按钮触发ClassOver()

void (Teacher::*teacherSignal2)() = &Teacher::hungry;
void (Student::*studentSlot2)() = &Student::treat;
connect(te, teacherSignal2, st, studentSlot2); // 连接无参数版本

connect(btn, &QPushButton::clicked, te, teacherSignal2); // 信号直接连接信号

disconnect(te, teacherSignal2, st, studentSlot2); // 断开信号
}

void Widget::ClassOver()
{
// emit te->hungry(); // 触发信号
emit te->hungry("宫保鸡丁");
}

信号也可以直接连接信号。

可以使用disconnect()断开信号。

注意点:

  • 一个信号可以连接多个槽函数。

  • 多个信号可以连接同一个槽函数。

  • 信号与槽函数的参数类型必须一一对应

  • 信号的参数数量可以多于槽函数的参数数量(但匹配的部分参数类型必须一一对应)

信号槽经常和Lambda表达式结合使用。

Qt4及以前版本的信号槽连接

1
2
connect(te, SIGNAL(hungry()), st, SLOT(treat()));
connect(te, SIGNAL(hungry(QString)), st, SLOT(treat(QString)));

优点:参数直观

缺点:编译时类型不做检测