Qt学习笔记

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链接

你可能感兴趣的:(Qt学习笔记)