|
本帖最后由 正点原子 于 2021-8-11 12:29 编辑
1)实验平台:正点原子i.MX6ULL Linux阿尔法开发板
2) 章节摘自【正点原子】《I.MX6U嵌入式Qt开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=603672744434
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子Linux技术交流群:1027879335
第五章Qt 信号与槽
在这章节里,我们学习Qt的信号与槽,这里分一个章节来学习这个Qt的信号与槽,可见这个信号与槽有多么重要。在学习Qt的过程中,信号与槽是必不可少的部分,也是Qt编程的基础,是Qt编程的一大创新(其实与C#的事件很相似,编程都是类似的),Qt的信号与槽在Qt4时或者更早前已经出现,并不是属于哪个版本的。只是Qt4与Qt5的信号槽连接的写法有些区别。本教程对Qt4的信号与槽连接写法不做讲解。
同时,由此章节开始,我们将不使用Qt Designer的方式进行开发,也可以在新建项目时把*ui文件不勾选。为什么不用Qt Designer方式开发程序了呢?Qt Designer方式开发程序简单,优点就是方便快捷,ui文件的优点就是能比较直观快捷的看到整体的布局。
但是缺点也很明显!简单的一两个部件还是可以的,但是控件多了就不好管理,每次都要打开ui文件添加新控件,不好用代码管理!读者也无从知道编者是如进行界面布局的,且信号槽的连接方式是生成代码之后才能在setupUi函数里才能看到,或者需要进入Ui设计器里的信号槽模式里才能看到信号槽的连接,这样十分之不方便!没有代码那么直观!所以统一用代码绘界面,可以锻炼我们的布局能力,和代码逻辑能力!
本章的内容如下:
5.1小节里我们学习Qt信号与槽机制的定义。
5.2小节开始我们就学习如何在项目里定义我们的信号与槽的创建和使用方法。
5.1 Qt信号与槽机制
信号与槽(Signal & Slot)是 Qt 编程的基础,也是 Qt 的一大创新。因为有了信号与槽的编程机制,在Qt中处理界面各个组件的交互操作时变得更加直观和简单。
信号(Signal)就是在特定情况下被发射的事件,例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。
GUI 程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。
槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
信号与槽关联是用 QObject::connect() 函数实现的,其基本格式是:
- QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
复制代码
connect() 是 QObject 类的一个静态函数,而 QObject 是所有 Qt 类的基类,在实际调用时可以忽略前面的限定符,所以可以直接写为:
- connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
复制代码
其中,sender 是发射信号的对象的名称,signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。
SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。例如,在4.1小节的项目02_designer_example的ui_mainwindow.h 文件(ui_mainwindow.h文件是编译后产生的,位于build-02_designer_example-Desktop_Qt_5_12_9_GCC_64bit-Debug文件夹目录下)中,在 setupUi() 函数中有如下的语句:
- QObject::connect(pushButton, SIGNAL(clicked()), MainWindow, SLOT(close()));
复制代码
其作用就是将pushButton按钮的 clicked() 信号与窗体(MainWindow)的槽函数 close() 相关联,这样,当单击pushButton按钮(就是界面上的“X”按钮)时,就会执行MainWindow的 close() 槽函数。在这里我们就可以解决4.1.3小节的中部分疑问了。我们使用第一种方法没看到信号与槽是怎么连接的,实际上它在UI文件编译后在生成的代码ui_mainwindow.h里!
关于信号与槽的使用,有以下一些规则需要注意:
一个信号可以连接多个槽,例如:
- connect(pushButton, SIGNAL(clicked()), this, SLOT(hide());
- connect(pushButton, SIGNAL(clicked()), this, SLOT(close());
复制代码
这是当一个对象pushButton的被单击时,所在窗体有两个槽进行响应,一个hide()用于隐藏主窗体,一个close用于关闭主窗体。这里只是举个例子,实际上当执行close()后,hide()这个槽已经没有什么意义了,因为已经程序退出了,但是实际它有执行,因为它连接在close()的前面。当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行。
当信号和槽函数带有参数时,在 connect()函数里,要写明参数的类型,但可以不写参数名称。
多个信号可以连接同一个槽,例如在02_designer_example里,我们再拖2个pushButton到设计的窗体里。同时也让它按4.1小节项目里的第一种方法连接信号与槽,把三个按钮的clicked()信号都连接到close()槽函数里。编译后可以在ui_mainwindow.h这个文件里的setupUi()函数里,有如下的信号槽连接语句。
- connect(pushButton,SIGNAL(clicked()),this,SLOT(close()));
- connect(pushButton_2,SIGNAL(clicked()),this,SLOT(close()));
- connect(pushButton_3,SIGNAL(clicked()),this,SLOT(close()));
复制代码
这样,当任何一个 pushButton 被单击时,都会执行close()函数,进而关闭或者退出程序。
一个信号可以连接另外一个信号(说明了connect万物皆可连,非常好用!),例如:
- connect(pushButton, SIGNAL(objectNameChanged(QString)),this, SIGNAL(windowTitelChanged(QString)));
复制代码
这样,当一个信号发射时,也会发射另外一个信号,实现某些特殊的功能。
严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
在使用信号与槽的类中,必须在类的定义中加入宏 Q_OBJECT(特别重要)。
当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。
总结如下图,可以看到发送者与发送的信号是在一起的,接收者与接收的信号/槽是在一起的。它们不能在connect()方法里写乱顺序!由发送者发送出信号到接收者用信号/槽接收。
信号槽连接的方法已经讲解了。这里只是稍稍提一下,断开连接的方法,初学者基本用不到断开连接的操作。使用disconnect()。disconnect(),这个方法重载了好几个函数,解开格式如下。
bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
断开一切与myObject连接的信号或槽。
- disconnect(myObject, 0, 0, 0);
复制代码
相当于非静态重载函数:
断开所有连接到特定信号的东西。
- disconnect(myObject, SIGNAL(mySignal()), 0, 0);
复制代码
相当于非静态重载函数:
- myObject->disconnect(SIGNAL(mySignal()));
复制代码
与指定的接收者断开连接。
- disconnect(myObject, 0, myReceiver, 0);
复制代码
相当于非静态重载函数:
- myObject->disconnect(myReceiver);
复制代码
信号与槽机制是 Qt GUI 编程的基础,使用信号与槽机制可以比较容易地将信号与响应代码关联起来。
5.2 如何在项目里创建信号
我们先新建一个项目,并把项目命名为03_signal_slot_example,如果还不会新建项目,请回到3.6小节查看项目如何建立的。只是新建项目命名为03_signal_slot_example,同时取消勾选*ui文件(为什么取消?可以在第五章章节开头处找理由),其他步骤不变。
由于信号只需声明,无需定义。所以我们只需要在mianwindow.h里声明信号即可。代码如下,如下图黑色加粗部分代码就是创建的信号。这里创建一个void pushButtonTextChanged();信号,定义信号最好是贴合信号本身的含义。编者定义这个信号的意思是按钮的文本发生改变后的signal。
mainwindow.h添加信号后的代码
- 1 #ifndef MAINWINDOW_H
- 2 #define MAINWINDOW_H
- 3
- 4 #include <QMainWindow>
- 5 /* 引入QPushButton */
- 6 #include <QPushButton>
- 7
- 8 class MainWindow : public QMainWindow
- 9 {
- 10 Q_OBJECT
- 11
- 12 public:
- 13 MainWindow(QWidget *parent = nullptr);
- 14 ~MainWindow();
- 15
- 16 signals:
- 17 /* 声明一个信号,只需声明,无需定义 */
- 18 void pushButtonTextChanged();
- 19
- 20 };
- 21 #endif // MAINWINDOW_H
复制代码
第16至18行,声明一个信号void pushButtonTextChanged();,可以看到信号无需public等关键字修饰。
5.3 如何在项目中创建槽
创建槽的方法也很简单,也是直接在mianwindow.h里直接声明槽,在mianwindow.cpp里实现槽的定义,声明槽必须写槽的定义(定义指函数体的实现),否则编译器编译时将会报错。
槽有以下特点:
1.槽可以是任何成员函数、普通全局函数、静态函数
2.槽函数和信号的参数和返回值要一致
根据上面的槽特点,由于我们在5.2小节里声明了信号void pushButtonTextChanged();所以我们声明的槽函数必须是无返回值类型void,和无需参数。所以声明槽的代码如下。此外我们还声明一个QPushButton对象pushButton。对象pushButton可以写成简写btn。这个根据个人习惯即可!简写的名称建议不要让人看不懂即可!同时还声明一个按钮点击的槽。
mainwindow.h添加槽函数后的代码
- 1 #ifndef MAINWINDOW_H
- 2 #define MAINWINDOW_H
- 3
- 4 #include <QMainWindow>
- 5 /* 引入QPushButton */
- 6 #include <QPushButton>
- 7
- 8 class MainWindow : public QMainWindow
- 9 {
- 10 Q_OBJECT
- 11
- 12 public:
- 13 MainWindow(QWidget *parent = nullptr);
- 14 ~MainWindow();
- 15
- 16
- 17 signals:
- 18 /* 声明一个信号,只需声明,无需定义 */
- 19 void pushButtonTextChanged();
- 20
- 21 public slots:
- 22 /* 声明一个槽函数 */
- 23 void changeButtonText();
- 24
- 25 /* 声明按钮点击的槽函数 */
- 26 void pushButtonClicked();
- 27
- 28 private:
- 29 Ui::MainWindow *ui;
- 30 /* 声明一个对象pushButton */
- 31 QPushButton *pushButton;
- 32 };
- 33 #endif // MAINWINDOW_H
复制代码
第23行,声明一个槽函数void changeButtonText();。
第26行,声明了一个槽函数void pushButtonClicked();。
第31行,声明了一个QPushButton对象pushButton。
在mainwindow.cpp里实现声明的槽函数void changeButtonText();和void pushButtonClicked();。同时还实例化了pushButton对象。代码如下。
mainwindow.cpp添加槽的实现代码
- 1 #include "mainwindow.h"
- 2
- 3 MainWindow::MainWindow(QWidget *parent)
- 4 : QMainWindow(parent)
- 5 {
- 6 /* 设置窗体的宽为800,高为480 */
- 7 this->resize(800,480);
- 8
- 9 /* 实例化pushButton对象 */
- 10 pushButton = new QPushButton(this);
- 11
- 12 /* 调用setText()方法设定按钮的文本 */
- 13 pushButton->setText("我是一个按钮");
- 14 }
- 15
- 16 MainWindow::~MainWindow()
- 17 {
- 18
- 19 }
- 20
- 21 /* 实现按钮点击槽函数 */
- 22 void MainWindow::pushButtonClicked()
- 23 {
- 24 /* 使用emit发送信号 */
- 25 emit pushButtonTextChanged();
- 26 }
- 27
- 28 /* 实现按钮文本改变的槽函数 */
- 29 void MainWindow::changeButtonText()
- 30 {
- 31 /* 在槽函数里改变按钮的文本 */
- 32 pushButton->setText("被点击了!");
- 33 }
复制代码
第7行,设置程序窗体的大小,假若不设置,窗体为最小分辨率。这里设置宽为800,高为480。刚好是正点原子RGB LCD屏里其中一个分辨率。实际开发需要以硬件的实际分辨率开发即可。本教程都是以800*480分辨率开发。不建议使用低于这个分辨率开发,分辨率太低,显示的内容过少。
第10行,实例化pushButton对象,在堆中实例化,并指定父对象为this。因为我们在mainwindow.h里声明的是指针类型的对象。实际上QPushButton(this)的原型是QPushButton(QWidget *parent = nullptr)。在C++基础里第2.2.3小节里就已经学过重载,实际上QPushButton 类中还有下面两种初始化的方法。这也是C++在Qt里重载的体现。
- QPushButton(const QString &text, QWidget *parent = nullptr)
- QPushButton(const QIcon &icon, const QString &text, QWidget *parent = nullptr)
复制代码
在22行和29行里,实现了槽方法。
5.4 如何在项目中连接信号与槽
5.2和5.3小节只是声明了信号和定义槽,要在项目中连接信号与槽。完成连接的代码如下。
信号槽连接的代码如下。
- connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
- connect(this, SIGNAL(pushButtonTextChanged()), this, SLOT(changeButtonText()));
复制代码
注意,发送信号的对象,和接收的信号的对象。因为我们pushButtonClicked()是本类里定义的槽,所以用this来接收。同理,pushButtonTextChanged()也是本类定义的信号。所以发送者写成this。changeButtonText()也是本类的槽函数,所以接收槽的对象也是this。很多初学者不了解这里的信号的发送者和槽的接收者,究竟该写谁。我们要细读上面的代码和5.1小节的信号槽机制的定义慢慢理解。
在mainwindow.cpp中信号槽连接的代码如下。
mainwindow.cpp实现连接信号槽
- 1 #include "mainwindow.h"
- 2
- 3 MainWindow::MainWindow(QWidget *parent)
- 4 : QMainWindow(parent)
- 5 {
- 6 /* 设置窗体的宽为800,高为480 */
- 7 this->resize(800,480);
- 8
- 9 /* 实例化pushButton对象 */
- 10 pushButton = new QPushButton(this);
- 11
- 12 /* 调用setText()方法设定按钮的文本 */
- 13 pushButton->setText("我是一个按钮");
- 14
- 15 /* 信号与槽连接 */
- 16 connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
- 17 connect(this, SIGNAL(pushButtonTextChanged()), this, SLOT(changeButtonText()));
- 18 }
- 19
- 20 MainWindow::~MainWindow()
- 21 {
- 22
- 23 }
- 24
- 25 /* 实现按钮点击槽函数 */
- 26 void MainWindow::pushButtonClicked()
- 27 {
- 28 /* 使用emit发送信号 */
- 29 emit pushButtonTextChanged();
- 30 }
- 31
- 32 /* 实现按钮文本改变的槽函数 */
- 33 void MainWindow::changeButtonText()
- 34 {
- 35 /* 在槽函数里改变按钮的文本 */
- 36 pushButton->setText("被点击了!");
- 37 }
复制代码
第16、17行,连接信号与槽,整个流程就是当点击了按钮,然后触发了pushButtonClicked(),pushButtonClicked()槽里发送pushButtonTextChanged()信号,changeButtonText()槽响应pushButtonTextChanged()信号,我们在changeButtonText()槽实现响应的动作(事件)。最终的实现效果是按钮的文本由“我是一个按钮”被点击时变成“被点击了!”。
编译程序及运行后,未点击按钮前如下。
点击按钮后。
上面的程序虽然简单,但是我们也最终实现了自定义的信号与槽。
5.5 学会使用Qt类的信号与槽
Qt里有大量的信号与槽,都是Qt自定义好的。基本够我们使用,如果没有找到想要的信号与槽,我们就可以按5.2~5.4小节自定义信号与槽的方法去定义自己的信号和槽了。那么我们该如何使用Qt信号与槽呢?
要想使用Qt的信号与槽,那么我们必须知道有哪些信号与槽。在5.4小节的代码里。
- connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
复制代码
如下图示,按住Ctrl键,再点击clicked(),进入clicked()这个信号的定义处。
进入QPushButton的定义处,我们看到QPushButton不止clicked信号,还有其他信号,也有QPushButton的槽函数(返回上一步按Alt + 方向左键)。在这里我们只是简单的看了如何在已知信号和槽里查找其他信号与槽。实际上在开发中我们经常需要使用Qt帮助文档来查看Qt定义的信号与槽。我们将在下一章里讲解Qt帮助文档的使用,帮助文档里面就有如何查看Qt的信号与槽等等。
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
该献的血还是要献的。你不献他不献。难道让我去献? --- 出自坛友:lovejp1981
|