QT学习入门(三)——信号与槽

2019-10-02

学习了信号与槽,想起在学MFC中的事件对应的机制,给按钮绑定一个相应事件。在QT中信号槽就是观察者模式,当发生了某个时间以后,它就会发出一个信号(signal)。如果有人想跟这个信号有什么关联的话绑定就好了。

通过一个实例来学习信号与槽

在学习信号与槽之前,我们得知道一个函数,是用来绑定信号与槽的

/*
   sender: 发出信号的对象
   signal:发送对象所发出的信号
   receiver:接收信号的对象
   slot:接收对象在接收到信号之后所需要调用的函数
*/
connect(sender, signal, receiver, slot);

练习一 点击按钮关闭当前窗口


 //先创建一个Button,绑定在当前窗口并添加一个标题
 QPushButton  *  btn  =  new  QPushButton("关闭窗口",this);
 /*
   使用connect函数对时间以及按钮进行绑定
   btn->上面所new出来的按钮,代表发送信号的对象
   &QPushButton::clicked->QPushButton类中的信号,在帮助文档中可以查看,此处代表这该按钮被点击
   this->代表当前的类,Widget
   &Widget::close->Widget中的槽函数,意思为关闭当前窗口
*/
connect(btn,&QPushButton::clicked,this,&Widget::close);

执行这段代码后,窗口即被关闭。
这个练习是帮助我们熟悉connect函数以及button和widget中的一些信号和槽。

练习二 自定义信号与槽(无重载+无参)

我们的场景是老师同学上课,老师下课后,触发信号老师的hungry,学生给老师进行请客等操作。
老师的类 Teacher.h

#ifndef  TEACHER_H
#define  TEACHER_H
#include  <QObject>
class  Teacher  :  public  QObject
{
  Q_OBJECT
public:
  explicit  Teacher(QObject  *parent  =  nullptr);
signals:
  //自定义信号  写到signals下面
  //返回值是void,只需要申明不需要实现
  //可以有参数,可以重载
  void  hungry();//这个是无参信号
public  slots:
};
#endif  //  TEACHER_H

在添加信号时需要注意的是

  • 自定义信号 写到signals下面
  • 返回值是void,只需要申明不需要实现
  • 可以有参数,可以重载
    所以我们不需要去实现这个信号的功能,申明一下即可。
    学生的类 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:
  //返回值是void,需要声明,也需要实现
  //可以有参数,可以发生重载
  void  treat();
};
#endif  //  STUDENT_H

我们在申明槽的时候需要注意:

  • 返回值是void,需要声明,也需要实现
  • 可以有参数,可以发生重载
  • 槽函数可以不定义在slots下,定义在slots下增加代码的可读性
    所以我们需要看一下,对应的.cpp文件
    Student.cp
#include  "student.h"
#include  <QDebug>
Student::Student(QObject  *parent)  :  QObject(parent)
{
}
void  Student::treat(){
  qDebug()  <<  "请老师吃饭";
}

这就是我们在整个过程中所自定义的信号与槽,我们接下来在窗口中把他们绑定一下即可。
绑定之前我们学要把Teacher与Student在类中申明一下。
Widget.h

#ifndef  WIDGET_H
#define  WIDGET_H
#include  <QWidget>
#include  <teacher.h>
#include  <student.h>  
class  Widget  :  public  QWidget
{
  Q_OBJECT 
public:
  Widget(QWidget  *parent  =  nullptr);
  ~Widget();
  void  classOver();//用来触发信号的函数
private:
  Teacher  *  teacher;
  Student  *  student;  
};
#endif  //  WIDGET_H

Widget.cpp

#include  <QPushButton>
Widget::Widget(QWidget  *parent)
  :  QWidget(parent)
{
  //创建老师
  teacher  =  new  Teacher(this);
  //创建学生
  student  =  new  Student(this);
  //绑定hungry信号与treat槽函数
    connect(teacher,&Teacher::hungry,student,&Student::treat);
  //调用下课,触发hungry信号
  classOver();
}
Widget::~Widget()
{
}
//触发hungry这个信号
void  Widget::classOver(){
  emit  teacher->hungry();

我们在运行程序后,会看见在控制台打印出的请老师吃饭的字样。

练习三 自定义信号与槽(重载+有参)

我们练习完自定义信号槽之后,会发现如果只进行无参数传递以及响应会有一些缺陷,所以我们再来练习一下有参的信号。
我们的场景是在触发hungry信号时,便传递一种食物过去,最后在槽函数中接收到该参数并打印在控制台。
我们现在在练习二的代码基础上,在各个文件中添加以下代码:
Teacher.h

signals:
    //自定义信号 写到signals下面
    //返回值是void,只需要申明不需要实现
    //可以有参数,可以重载
    void hungry();
    void hungry(QString);//新添加的有参信号

Student.h

public  slots:
  //返回值是void,需要声明,也需要实现
  //可以有参数,可以发生重载
  void  treat();
  void  treat(QString);//新添加的有参槽函数

Student.cpp

//实现新添加的有参槽函数
void  Student::treat(QString  food){
  qDebug()  <<  "请老师吃饭"  <<  food.toUtf8().data();
}

我们现在要做的就是需要把有参槽函数和有参信号绑定在一起,但是如何去绑定是个新问题,因为我们对信号和槽函数进行了重载,有2个名字相同的信号和槽函数,所以我们需要用的函数指针来完成这个操作。
Widget.cpp

Widget::Widget(QWidget  *parent)
  :  QWidget(parent)
{
  //创建老师
  teacher  =  new  Teacher(this);
  //创建学生
  student  =  new  Student(this);
  //如果信号和槽有参数的话,需要使用函数指针
  void(Teacher::*teacherPara)(QString)  =  &Teacher::hungry;
  void(Student::*studentPara)(QString)  =  &Student::treat;
  //解耦信号和槽
  connect(teacher,teacherPara,student,studentPara);
  //触发信号
  classOver();
}
void  Widget::classOver(){
  emit  teacher->hungry("鸡公煲");
}

我们在这里使用到了函数指针,使函数指针指向了Teacher与Student中的带参信号和槽函数,在connect中直接绑定2个函数指针即可。
我们接下来运行代码,可以看到在在控制台输出的请老师吃饭 鸡公煲

练习四 信号链接信号

我们在练习三的基础上,来实现一个新的功能,就是点击一个按钮,触发下课,才在控制台中输出请老师吃饭
Widget.h

Widget::Widget(QWidget  *parent)
  :  QWidget(parent)
{
  //创建老师
  teacher  =  new  Teacher(this);
  //创建学生
  student  =  new  Student(this);
  //如果信号和槽有参数的话,需要使用函数指针
  void(Teacher::*teacherPara)(void)  =  &Teacher::hungry;
  void(Student::*studentPara)(void)  =  &Student::treat;
  //解耦信号和槽
  connect(teacher,teacherPara,student,studentPara);
  //触发信号
  //classOver();
  //通过信号去通知激发信号
  //新建一个button链接
  QPushButton  *  btn  =  new  QPushButton("请客吃饭",this);
  resize(600,400);
  //绑定一下,通过点击事件绑定到classOver,classOver中出发老师的带参信号
  connect(btn,&QPushButton::clicked,teacher,teacherPara);
}

在实现此代码的时候,请注意,请不要使用带参的老师信号,因为在connect绑定的时候需要注意:

  • 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
  • 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。
    此次练习,学习的是,信号链接信号,说白了就是信号可以触发信号,直到最后一个信号触发了槽函数

练习五 综合练习

点击一个名为open的按钮,点击后弹出一个窗口,open按钮变为close,点击close按钮,弹出的窗口关闭
没有Lambda表达式写起来有些麻烦,先把无使用Lambda写出来。
Widget.h

#ifndef  WIDGET_H
#define  WIDGET_H
#include  <QWidget>
#include  <QPushButton>
QT_BEGIN_NAMESPACE
namespace  Ui  {  class  Widget;  }
QT_END_NAMESPACE 
class  Widget  :  public  QWidget
{
  Q_OBJECT
public:
  Widget(QWidget  *parent  =  nullptr);
  ~Widget();
  void  trigger();//触发函数
  void  btnEvent(bool  *  flag);//槽
signals:
  void  isWin(bool  *  flag);//信号 
private:
  bool  flag;//判断窗口打开没有
  Widget  *  w;//新建的窗口
  QPushButton  *  btn;//按钮
};
#endif  //  WIDGET_H

Widget.cpp

#include  "widget.h"
#include  "ui_widget.h" 
Widget::Widget(QWidget  *parent)
  :  QWidget(parent)
{
  btn  =  new  QPushButton("open",this);
  btn->setText("open1");
  resize(600,400);
  //一开始窗口没打开,为false
  flag  =  false;
  //把点击事件和触发函数绑定一下
  connect(btn,&QPushButton::clicked,this,&Widget::trigger);
  //把信号和槽函数帮顶一下
  connect(this,&Widget::isWin,this,&Widget::btnEvent);
} 
Widget::~Widget()
{
}
//其实这个才是真正的槽函数
void  Widget::btnEvent(bool  *  flag){
  if  (*flag){
  //打开状态,那么窗口关闭
  w->close();
  *flag  =  false;
  btn->setText("open");
  }else  {
  //新建一个窗口
  w  =  new  Widget();
  //展示一下
  w->show();
  *flag  =  true;
  btn->setText("close");
  }
}
//触发槽函数
void  Widget::trigger(){
  //触发这个信号
  emit  isWin(&flag);
  }

运行代码后,会发现点击open后会有个新的窗口覆盖在原先的窗口上,移开后,点击close,窗口关闭。
##总结一下

  • 发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外)
  • 信号和槽函数返回值是 void
  • 信号只需要声明,不需要实现
  • 槽函数需要声明也需要实现
  • 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响
  • 使用 emit 在恰当的位置发送信号
  • 使用connect()函数连接信号和槽
  • 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
  • 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致
  • 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。
  • 一个信号可以和多个槽相连(如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。)
  • 多个信号可以连接到一个槽
  • 一个信号可以连接到另外的一个信号
  • 槽可以被取消链接(这种情况并不经常出现,因为**当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽)
  • 信号槽可以断开(利用disconnect关键字是可以断开信号槽的)
  • 使用Lambda 表达式。在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。

标题:QT学习入门(三)——信号与槽
作者:MrDalili
地址:https://www.ningdali.com/articles/2019/10/02/1570003514771.html

评论
发表评论