Qt笔记
本笔记适用于Qt5,教材来源为哔哩哔哩Up主“爱编程的大丙”,视频地址:https://www.bilibili.com/video/BV1Jp4y167R9?p=31&spm_id_from=pageDriver
## Qt下载和安装
Qt在其官网即可下载,下载链接为:`http://download.qt.io/archive/qt/`,可根据自己的系统版本下载对应的安装包。
下载完成之后,在Linux系统中,首先要将安装包权限改为可执行文件:
```powershell
sudo chomd +x xxxx.run
```
然后运行安装命令进行安装:
```powershell
./xxxx.run
```
运行之后会弹出安装界面,会提示需要账号和密码,因此需要提前去官网注册好,输入完成之后一路下一步即可,此处不再赘述。
## Qt工程目录及含义
### Qt工程创建基本流程
安装完成后打开Qt,在File选项中选择新建文件或工程,然后选择新建工程中的Project->Application,选择Qt Widgets Application选项,如下图所示:
![](./02.gif)
然后点击choose进入下一步,设置项目名称和存储地址,进入下一步,Build system选择qmake,Classname设定之后继续下一步,剩下的一路下一步即可,其他的都不用操作。
Qt工程目录分为四个部分:
- project文件:描述Qt项目的文件及依赖关系
- Headers:QT项目中的头文件
- Sources:Qt项目中的源文件
- Forms:Qt项目中的ui文件,其可用QtDesinger打开编辑
## 信号和槽
### 概述
所谓信号槽,实际就是ROS的发布者和订阅者的模式,分为信号和槽两部分。
当某个事件发生之后对应的对象发出特定信号(Signal),这个信号没有明确的接收者,所有接收到这个信号的观察者只要需要都可以通过connect函数对这个信号做出响应,将想要处理的信号和自己的一个函数(称为槽,slot)绑定来处理这个信号,也就是说**当信号发出时,被连接的槽函数会自动被回调**,这就类似观察者模式:当感兴趣的事发生时,某一个操作就会被自动触发。
### 信号槽本质
#### 信号本质
信号是由于用户对窗口或控件进行了某项操作,导致窗口或控件产生了某个特定事件,这时Qt对应的窗口类会发出某个信号,以此对用户的操作做出反应。
因此根据上述的描述我们得到一个结论:信号的本质就是事件。比如:
- 单击、双击按钮
- 窗口刷新
- 鼠标移动、按下、释放
- 键盘输入
那么在Qt中信号通过什么方式呈献给用户呢?
- 我们对哪个窗口进行操作,该窗口就可以捕捉到这些被触发的事件
- 对于使用者来说触发了一个事件我们就可以得到Qt框架给我们发出的某个特定信号
- 信号的呈现形式就是函数,也就是说某个事件产生了,Qt框架就会调用某个对应的信号函数,通知使用者。
在Qt中信号的发出者是**某个实例化的类对象**,对象内部可以进行相关事件的检测。
#### 槽本质
在Qt中槽是一种特殊的功能函数,在编码中,也可以作为类的普通函数来使用。之所以称为槽函数,是因为他们还有一个职责就是对Qt框架中产生的信号进行处理。
例如:
女朋友(一种不存在的东西)说:“我饿了”,于是我带她去吃饭
上面例子中相当于女朋友发出了一个信号,我收到了信号并将其处理掉了。
- 女朋友->发送信号的对象
- 信号内容:我饿了
- 我->接收信号的对象
- 处理方式:带她去吃饭
在Qt中槽函数的所有者也是**某个类的实例对象。**
#### 信号和槽的关系
在Qt中信号和槽都是独立的个体,本身没有任何联系,但由于某种特性需求我们可以将二者连接到一起,好比牛郎和织女需要喜鹊为他们搭桥一样。在Qt中我们需要QObject中的connect函数进行二者的链接。
connect函数的函数原型:
```C++
QMetaObject::Connection QObject::connect(const QObject *sender,
const char *signal,
const QObject *receiver,
const char *method,
Qt::ConnectionType type = Qt::AutoConnection)
```
参数:
- sender:发出信号的对象
- signal:属于sender对象,信号是一个函数,**这个参数的类型是函数指针,指向信号函数的地址**
- receiver:接收信号的对象
- method:属于receiver对象,当检测到sender发出了signal信号,receiver对象调用method方法,进行信号发出之后的处理动作
注意事项:
- connect函数相当于对信号处理动作的注册;
- 调用connect函数的sender对象的信号并没有马上产生,因此receiver对象的method函数也不会马上被调用;
- method槽函数的本质是一个回调函数,调用的时机是信号产生之后,调用是Qt框架来执行的;
- connect中的sender和receiver两个指针必须被实例化了,否则connect不会成功
函数原型精简之后可以写作:
```C++
connect (
const QObject *sender,const &QObject::signal,
const QObject *receiver,const &QObject::method
)
```
## 标准信号槽使用
### 标准信号/槽
Qt提供的很多标准类中红都可以对用户触发的某些信号进行检测,因此当用户做了这些操作以后,事件被触发,类的内部就会产生对应的信号,这些信号都是Qt类内部自带的,因此称之为标准信号。
同样的,在Qt的很多类内部为我们提供了很多功能函数,并且这些功能函数也可以作为触发的信号的处理动作,有这类性质的函数在Qt中称之为标准槽函数。
系统自带的信号和槽如何查找呢?可以通过帮助文档,如下图所示:
#### 使用举例
实现点击按钮关闭窗口的功能。
设计对象:按钮和窗口。
- 按钮发出点击信号->`QPushbutton`,`QPushbutton::clicked`
- 窗口接收点击信号并进行处理->`this`,`QMainwindow::close`
首先在创建的工程中的ui文件夹Forms中双击mainwindow.ui进行编辑,在其中添加一个按钮:
![](./03.png)
在其中修改两个部分:
1. PushButton的名字,
2. PushButton的objectName选项
修改之后的界面如下图所示:
![](./07.png)
然后对source中的mainwindow.cpp文件进行编辑,加入如下代码片段
```C++
connect(ui->closeButton,&QPushButton::clicked,this,&QMainWindow::close);
//信号发出对象为ui->closeButtopn按钮,发送的信号为&QPushButton::clicked
//信号接收对象为this,也就是本窗口,接收信号后的处理槽函数为&QMainWindow::close
//注意,connect函数中的信号函数和槽函数参数分别是这两个函数的地址。
```
然后编译程序并运行,其界面如下:
![](./04.png)
### 自定义信号/槽
Qt框架提供的信号槽在某些特定的场景下无法满足我们的项目需求,因此我们还需要设计自己需要的信号和槽,同样还是使用connect函数对自定义的信号槽进行链接
如果要使用自己定义的信号槽,首先要编写新的类并让其继承Qt的某些标准类,如果我们自己编写的类想要在Qt中使用信号槽机制,那么必须要满足如下条件:
- 这个类必须从QObject类或者是其子类进行派生
- 在自定义的头文件中加入Q_OBJECT宏
在头文件派生的时候,首先像下面那样引入Q_OBJECT宏:
```C++
class MyMainWindow : public QWidget
{
Q_OBJECT
......
}
```
#### 自定义信号
自定义信号的要求:
- 信号是类的成员函数
- 返回值必须是void类型
- 信号的名字可以根据实际情况进行指定
- 信号可以随意指定,信号也支持重载
- 信号需要使用signal关键字进行生命,使用方法类似于public等关键字
- 信号函数只需要生命,不需要定义
- 在程序中发送自定义信号:发从信号的本质就是调用信号函数,习惯性在信号前面加上关键字emit,emit提示这是在发送信号,没有其他含义和功能
举例:信号重载
Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
```C++
class test:public QObject
{
Q_OBJECT
signals:
void testsignal();
void testsignal(int a); //重载
};
```
#### 自定义槽
槽就是信号的处理函数,自定义槽和自定义其他函数一样,没什么区别
自定义槽的要求:
- 返回值是void类型
- 槽也是函数,也支持重载:
- 槽函数需要指定多少个参数,需要看链接的信号的参数个数
- 槽函数的参数是用来接收信号发送的数据的,信号发送的数据就是信号的参数。例如:
- 信号函数:`void testsig(int a , double b)`
- 槽函数:`void testslot(int a , double b)`
- 总结:
- 槽函数的参数应该和对应的信号的参数个数和数据类型一一对应
- 信号的参数可以大于等于槽函数的参数个数,信号传递的数据被忽略了
- 信号函数:void testsig(int a, double b);
- 槽函数:void testslot(int a);
- Qt中槽函数的类型:
- 类的成员函数
- 全局函数
- 静态函数
- labmda表达式(匿名函数
- 槽函数可以使用关键字进行声明:slots(Qt5中可以忽略不写)
- public slots
- private slots
- protected slots
举例:类中的这三个函数都可以作为槽函数来使用:
```C++
class Test:public QObject
{
public:
void testSlot();
static void testFunc();
public slots:
void testSlots(int id);
};
```
场景举例:女朋友饿了 我请他吃饭
```c++
class GirlFriend;
class Me;
```
创建自定义类流程:
1. 首先在项目名右键点击Add New,在弹出的窗口选择C++类,
2. 在define class 中填入类名并选择父类QObject:
![](./05.png)
3. 最后点击下一步完成创建。
创建完成之后首先修改GirlFriend类,在头文件中的signals中添加函数hungry:
```c++
//GirlFriend.h
#ifndef GIRLFRIEND_H
#define GIRLFRIEND_H
#include
class GirlFriend : public QObject
{
Q_OBJECT
public:
explicit GirlFriend(QObject *parent = nullptr);
signals:
void hungry();
};
#endif // GIRLFRIEND_H
```
此处需注意,**写在signals里的hungry函数不需要定义,只需要声明就好了**。
然后在Me类中添加处理动作,编辑Me.h,在其中加入处理动作(槽函数)声明:
```c++
public slots:
void feed();
```
在Me.cpp文件中添加槽函数的定义:
```c++
void Me::feed()
{
qDebug()<<"let's go for food!"< } ``` 编辑完成之后的文件如下: ```c++ //Me.h #ifndef ME_H #define ME_H #include class Me : public QObject { Q_OBJECT public: explicit Me(QObject *parent = nullptr); public slots: void feed(); }; #endif // ME_H ``` ```C++ //Me.cpp #include "me.h" #include Me::Me(QObject *parent) : QObject(parent) { } void Me::feed() { qDebug()<<"let's go for food!"< } ``` 最后在窗口中用connect来链接这两个对象。 首先在头文件中添加这两个对象: ```C++ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include "me.h" #include "girlfriend.h" #include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; Me *m_me; GirlFriend *m_girl; }; #endif // MAINWINDOW_H ``` 最后在CPP文件中链接二者: ```C++ connect(m_girl,&GirlFriend::hungry,m_me,&Me::feed); ``` 此时有一个问题,hungry信号是自定义的,框架无法直接自动发送,因此需要设计一些触发机制触发hungry信号,这里使用按钮触发信号。 打开ui文件,添加新的按钮如下: ![](./06.png) 并在mainwindow文件中添加按钮和hungry信号的链接: ```C++ connect(ui->hungryButton,&QPushButton::clicked,this,&MainWindow::hungry_slot); ``` 信号发出者为按钮,发出信号为按钮按下,信号接受者为m_girl,接收信号后的处理函数为&GirlFriend::hungry,即触发hungry函数。 其中`MainWindow::hungry_slot`函数为自定义的槽函数,其作用是使m_girl对象发送hungry信号: ```c++ void MainWindow::hungry_slot() { emit m_girl->hungry();//加不加emit没区别,唯一的作用就在于提示程序员现在是在发送信号 } ``` ### 信号槽拓展 #### 信号槽使用拓展 1. 一个信号可以链接多个槽函数,发送一个信号可以有多个处理动作 - 需要写多个connect函数 - 槽函数的执行顺序是随机的,和connect函数的调用顺序没有关系 - 信号的接收者可以使一个对象,也可以是多个对象 2. 一个槽可以连接多个不同的信号 3. 信号可以连接信号, - 信号接收者可以不处理接收的信号,继续发出新的信号,其传递了参数,但并未对其进行处理。 ```C++ connect(m_girl,&GirlFriend::hungry,m_me,&Me::feed); connect(ui->hungryButton,&QPushButton::clicked,this,&MainWindow::hungry_slot); ``` 精简为: ```C++ connect(ui->hungryButton,&QPushButton::clicked,m_girl,&GirlFriend::hungry); ``` 4. 信号槽是可以断开的 ```C++ disconnect ( const QObject *sender,const &QObject::signal, const QObject *receiver,const &QObject::method ) ``` 断开信号槽和链接信号槽除了函数名不同其他完全相同。 #### 信号槽的两种连接方式 1. Qt5的连接方式:推荐的使用方式 ```C++ connect(ui->hungryButton,&QPushButton::clicked,m_girl,&GirlFriend::hungry); ``` 2. Qt4的连接方式:不推荐的使用方式 ```C++ connect(ui->hungryButton,SIGNAL(QPushButton::clicked()),m_girl,SLOT(GirlFriend::hungry())); ``` ### 信号槽之间如何传递信号 Qt5信号和槽的连接方式如下: ```C++ connect(ui->hungryButton,&QPushButton::clicked,m_girl,&GirlFriend::hungry); ``` 这里面看不到信号是如何传递的,那两个函数的信号如何传递呢?是通过两个函数的参数来传递的。 如,信号函数为: ```c++ void hungry(QString msg); ``` 则槽函数可以写为: ```c++ void eat(QString msg); ``` 然后在触发信号函数中使用函数`hungry("XXXX")`,框架会将`"XXXX"`从hungry函数传递到eat函数,实现参数传递。 这里存在一个问题,如果是Qt4的链接方式,链接函数可写为: ```C++ connect(m_girl,SIGNAL(GirlFriend::hungry(QString)),m_me,SLOT(Me::feed(String))); ``` 这种调用方式可以明确的指出调用的是函数的哪个重载类型,这里就可以直接运行了。 但是对于QT5的连接方式,其链接函数写为: ```C++ connect(m_girl,GirlFriend::hungry,m_me,Me::feed); ``` 这种情况会报错,因为编译器搞不清楚你到底想调用signal和slot函数的哪个重载类型,针对这个问题有两个解决方法: 1. 用Qt4的调用方法; 2. 创建函数指针,指明到底要采用哪个函数重载类型。 一般选择第二种方法,其代码如下: ```C++ void (GirlFriend::*girl_1)()=&GirlFriend::hungry; void (GirlFriend::*girl_2)(QString msg)=&GirlFriend::hungry; void (Me::*feed_1)()=&Me::feed; void (Me::*feed_2)(QString msg)=&Me::feed; connect(m_girl,girl_1,m_me,feed_1); connect(m_girl,girl_2,m_me,feed_2); ``` ## QT界面与ROS链接