Qt(信号槽机制)
信号槽
1.概念
信号和槽是两个函数,这是Qt在C++基础上新增的特性。类似于其他技术中的回调的概念。当某个事件发生,就执行另一个事件。
特点:
(1).信号和槽是两个函数
(2).通信的对象必须是从QObject类中派生而来的
(3).类中必须有Q_OBJECT宏
2.连接函数
在帮助中找到QObject类,找到右侧public slots函数,可以发现connect函数有五种传参方式,第一种是我们最常用的一种方式。
常用的函数:
connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)
参数1:发射者参数2:信号函数,表示发射者发生的动作,发生后发送消息。SIGNAL()包裹。
参数3:接收者
参数4:槽函数,表示接收者接收到消息后要做的事情。使用SLOT()包裹。
第五个参数type就是用来指定连接的类型。它可以是以下几种枚举值之一:
Qt::AutoConnection:Qt将自动选择连接类型。如果信号和槽在同一线程中,那么使用Qt::DirectConnection,否则使用Qt::QueuedConnection。
Qt::DirectConnection:直接连接。当信号发出时,槽函数会立即在发射信号的线程上被调用。这意味着如果信号和槽位于不同线程,那么可能会出现线程安全问题。
Qt::QueuedConnection:队列连接。当信号发出时,Qt会将事件放入接收对象的事件队列中,然后等待目标对象所在的线程处理该事件。因此,槽函数的执行将延迟到接收对象所在的线程中执行,这通常用于跨线程连接。
Qt::BlockingQueuedConnection:阻塞队列连接。与Qt::QueuedConnection类似,但是发送者将被阻塞,直到接收者处理完槽函数为止。
Qt::UniqueConnection:确保每个连接只建立一次,避免重复连接。
3.实现方式
信号槽主要分为三种实现方式:
- 自带信号→自带槽
- 自带信号→自定义槽
- 自定义信号
3.1 自带信号连接槽函数
概念:
这种方式是最简单的,因为信号函数和槽函数都使用的是Qt内置的,只需要在文档中查找出函数后,使用connect函数连接即可。
练习:
当按钮被点击关闭当前窗口
dialog.h:
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton *btn;
};#endif // DIALOG_H
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn = new QPushButton("关闭",this);btn->move(200,250);// 点击按钮,关闭窗口// 参数1:按钮对象// 参数2:点击信号函数clicked// 参数3:接收者窗口对象// 参数4:关闭窗口。槽函数closeconnect(btn,SIGNAL(clicked()),this,SLOT(close()));
}Dialog::~Dialog()
{delete btn;
}
3.2 自带信号连接自定义槽函数
概念:
Qt不可能内置所有执行的动作代码,特别是复杂的动作,需要开发者手动编写槽函数,这种方式也是所有连接方式中使用最多的。
槽函数实际上是一种特殊的成员函数,在声明的时候权限的作用主要是修饰其作为普通成员函数的使用效果,不影响信号槽的连接效果。
自定义槽函数的性质:
(1)本质是普通成员函数
(2)访问权限可以是任意一种,但是最好还是public slots:或private slots:
(3)参数要和信号函数一致
(4)可以被手动调用,和正常成员函数一样可以直接被调用
(5)必须是QObject的子类成员
(6)可以是普通成员函数、静态成员函数、lambda表达式
我们发现成员函数有的性质,槽函数都有,但是只有槽函数能在信号事件触发被调用,普通成员函数不能当做槽函数用。
练习:
【例子】现在我们来实现点击按钮,窗口向右和向下移动10个像素,同时输出当前的窗口坐标。
dialog.h:
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton *btn;//声明自定义槽函数
private slots: //最小权限法则,能用私有就用私有,其次保护,最后公有void mySlot(); //小驼峰:第一个单词首字母小写,其他单词首字母大写
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn = new QPushButton("移动",this);btn->move(200,250);connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}Dialog::~Dialog()
{delete btn;
}void Dialog::mySlot()
{// 获取当前窗口的坐标int x = this->x();int y = this->y();// 移动坐标位置move(x+10,y+10);// 输出当前坐标位置qDebug() << x+10 << y+10;
}
3.4 自定义信号
上面两种是用的最多的,但是自定义信号是在多线程和多窗口通信最重要的方式,现阶段单线程用的不多。现在用不是最优解,但是必须要学,只要是想从事Qt!
概念:
自定义信号是指你在自己写的类中声明的信号,用于在特定事件发生时通知其他对象。
它是Qt对象间通信的核心机制之一。
步骤:
(1)定义信号函数
只需要声明信号函数和确定参数,不需要定义,使用emit发射的时候默认携带参数发射信号。
dialog.h:
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton *btn;private slots: void mySlot(); void mySlotcValue(int);signals:void mySignal(); // 无参数信号void valueChanged(int value); // 带参数信号
};#endif // DIALOG_H
(2)发射(emit)自定义信号
定义了信号函数,要发送出去才能提现信号的性质,有参数就会把参数一起发给接受者。再定义一个槽函数去发射信号。
void Dialog::mySlot()
{// 发射自定义信号emit mySignal(); // 发射无参数信号emit valueChanged(123); // 发射带参数信号
}
(3) 连接信号函数和槽函数
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn = new QPushButton("杀鸡用牛刀",this);btn->move(200,250);connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));connect(this,SIGNAL(mySignal()),this,SLOT(close()));connect(this,SIGNAL(valueChanged(int)),this,SLOT(mySlotcValue(int)));
}Dialog::~Dialog()
{delete btn;
}void Dialog::mySlotcValue(int value){qDebug() << value;
}void Dialog::mySlot()
{// 发射自定义信号emit mySignal(); // 发射无参数信号emit valueChanged(123); // 发射带参数信号
}
这样就实现了点击按钮发送调用mySlot槽函数,发射了两个信号,一个关闭窗口,一个打印携带来的数据123。
4.信号槽传参
前言
其实上面我们已经实现传参了,信号槽传参就是发送信息者在触发某个事件发送一个信号给接受者,这个信号包含了一个数据,槽函数参数对应就可以调用到这个数据。上面我们按钮触发会发射两个信号,再把本窗口作为发送者,如果两个自定义信号函数被触发,就把信号发给本窗口,去执行两个任务。自己给自己传信号有点多次以举动,这是为后面多线程通信准备,所有上面说单线程就没必要用自定义信号。
但是使用一些Qt的信号函数进行传参对于单线程还是很重要的,因为还是属于上面第二种实现方式。
练习:
【例子】点击按钮,按钮上显式点击的次数,把上面第二种方式的代码直接改变而来,增加传参方式。
设置按钮显式的文字。
使用按钮类自带的成员函数函数可以设置按钮显示的文字,这里先给一个类型转换的方式,后面还会细讲,这个是吧int类型转换成QString类型,是静态成员变量,标注作用域就可以调用。
void setText(const QString & text)
// 类型转换
QString QString::number(int n, int base = 10)[static]
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton * btn;private slots:void mySlot();void mySlot2(int count);signals:void mySignal(int count);
};#endif // DIALOG_H
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn = new QPushButton("0",this);btn->move(200,250);connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));// connect函数,在建立连接时,如果信号或者槽函数有携带的参数,只写类型,不写具体的名称。connect(this,SIGNAL(mySignal(int)),this,SLOT(mySlot2(int)));
}void Dialog::mySlot()
{static int count = 0;count++;// 发射自定义信号,并将参数携带过去emit mySignal(count);
}void Dialog::mySlot2(int count)
{// 类型转换 int -> QStringQString text = QString::number(count);// 更改按钮文字btn->setText(text);
}Dialog::~Dialog()
{delete btn;
}
- 理论上来说可以传递任意多个参数,建议最多传递两个参数,多了会很冗余。如果非得传递多个参数,可以定义成一个类,传递对象。
- 信号的参数个数必须大于等于槽的参数个数。
- 信号的参数类型必须要与槽的参数类型匹配。
如果想传递多个参数可以封装成类进行传参:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Demo
{
public:int a = 10;int b = 20;double c = 3.23;float d = 1.1;
};class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton *btn;private slots:void mySlot(); // 自定义槽函数void mySlot2(const Demo &); // 与mySignal(int)连接的自定义槽函数signals:void mySignal(const Demo &); // 带参数的信号函数
};#endif // DIALOG_H
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn = new QPushButton("0",this);btn->move(150,150);connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));connect(this,SIGNAL(mySignal(const Demo &)),this,SLOT(mySlot2(const Demo &)));
}void Dialog::mySlot()
{Demo demo;// 发射带参数的自定义信号函数emit mySignal(demo);
}void Dialog::mySlot2(const Demo &demo)
{qDebug() << demo.a;qDebug() << demo.b;qDebug() << demo.c;qDebug() << demo.d;
}Dialog::~Dialog()
{delete btn;
}
5.对应关系
一对一就不用多说了,就是一个信号连接一个信号槽,上面就基本都是一对一关系。
5.1 一对多
概念:
一对多指的是一个信号连接多个槽函数。
对于一对多的连接,可以合并为一对一,因为槽函数也是一个成员函数,可以整合到一个槽函数中进行连接。
练习:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();QPushButton *btn;QPushButton *btn2;private slots:void mySlot1();void mySlot2();void mySlot3();
};#endif // DIALOG_H
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn = new QPushButton("一对多",this);btn->move(200,250);btn2 = new QPushButton("一对一",this);btn2->move(200,300);// 对于一对多连接方式的优势,可以灵活处理每个连接关系// 例如可以断开某个信号槽的连接// 断开连接与连接的函数非常相似,只需要在connect函数前面加一个disconnect(btn,SIGNAL(clicked()),this,SLOT(mySlot1()));connect(btn,SIGNAL(clicked()),this,SLOT(mySlot2()));// 断开btn与mySlot2的连接disconnect(btn,SIGNAL(clicked()),this,SLOT(mySlot2()));// 一对一实现一对多的连接方式。优点是,减少代码的编写// 不能够灵活处理每个对应关系connect(btn2,SIGNAL(clicked()),this,SLOT(mySlot3()));
}void Dialog::mySlot1()
{qDebug() << "A";
}
void Dialog::mySlot2()
{qDebug() << "B";
}void Dialog::mySlot3()
{mySlot1();mySlot2();
}Dialog::~Dialog()
{delete btn;
}
5.2 多对一
概念:
多对一指的是多个信号连接同一个槽函数,多对一的问题在于槽函数无法直接判断那个信号触发的槽函数调用,但是可以通过sender函数在槽函数中获得发射者对象,通过对象的比对判断来源。
多对一可以使用sender()函数确定发送信号的组建对象,但是sender()返回值是一个Object*类型,所以要直接使用必须类型转换,但是可以直接拿来比较确定是不是某个组建对象从而确定发送者是谁。
练习:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);~Dialog();private:QPushButton *btn1;QPushButton *btn2;private slots:void btnClickedSlot();
};#endif // DIALOG_H
#include "dialog.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{resize(500,500);btn1 = new QPushButton("多对一A",this);btn2 = new QPushButton("多对一B",this);btn1->move(200,250);btn2->move(200,300);// 多对一connect(btn1,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));connect(btn2,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{delete btn1;delete btn2;
}void Dialog::btnClickedSlot()
{// 通过sender函数可以获得发射者对象if(sender() == btn1){qDebug() << "A";}else if(sender() == btn2){qDebug() << "B";}else{qDebug() << "对象错误";}
}