信号与槽是Qt的一大创新,也是Qt编程的基础。本文记录一下该机制的学习笔记。
信号(Signal)
信号(Signal)是定义在类里面的一个函数,必须在函数前加入关键字signals,可以将其理解为宏,这个signals涉及到qt的另一个核心机制(模板元编程)。当拥有信号的类声明了一个对象时,该对象便有了发送该信号的能力,当然,该类的派生类也具有该能力。定义信号函数时,返回值必须是void,可以有形参,但是没有成员限定访问符的限定,我认为它是public,但是好像是真没有,定义一个signals:
signals:
void selectionChange(int index);
槽(Slots)
槽(Slots)就是对信号响应的函数。它是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。定义一个Slots:
public slots:
void setNewNum(int index);
连接(Connect)
信号与槽关联是用 QObject::connect() 函数实现的,其基本格式是:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
connect() 是 QObject 类的一个静态函数,而 QObject 是所有 Qt 类的基类,在实际调用时可以忽略前面的限定符,所以可以直接写为:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
其中,SIGNAL 和 SLOT 是 Qt 的宏,sender 是发射信号的对象的名称,signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。
原型
static QMetaObject::Connection connect(const QObject *sender, const char *signal,const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,const QObject *receiver, const QMetaMethod &method,
Qt::ConnectionType type = Qt::AutoConnection);
inline QMetaObject::Connection connect(const QObject *sender, const char *signal,const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;
第一个参数为发射信号的对象,例如后面的dlg;
第二个参数是要发射的信号,例如后面的SIGNAL(dlgReturn(int));
第三个参数是接受信号的对象,例如后面的this,表明是本部件,即Widget,当这个参数是this时,可以将其省略掉,因为在第三个函数中,该参数默认为this;
第四个参数是要执行的槽.
第五个参数一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。
-
Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
-
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
-
Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
-
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
-
Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
写法
Qt的connect函数有多重写法,为了方便演示,这里先自定义一个button。
class MyButton : public QWidget
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
signals:
void sigClicked();
void sigClicked(bool check);
};
- 第一种写法:老式写法
connect(m_pBtn,SIGNAL(sigClicked()),this,SLOT(onClicked()));
connect(m_pBtn,SIGNAL(sigClicked(bool)),this,SLOT(onClicked(bool)));
该写法的缺点是书写麻烦,优点是清晰明确。
- 第二种写法:Qt5新写法
connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, &Widget::onClicked);
//或
connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked),this,&Widget::onClicked);
- 第三种写法:lambda函数写法
connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked),
[=](bool check){
/* do something.. */
});
connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, [=](bool check){
//do something
});
当我们用lambda表达式的时候,槽的接收者QObject是可以省略不写的,这时候Qt会默认发射者与接收者属于同一个QObject。
连接方式
信号(Signal)与槽(Slots)的连接方式有多种:
- 一个信号可以连接多个槽
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));
- 多个信号可以连接同一个槽
connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
- 一个信号可以连接另外一个信号
connect(spinNum, SIGNAL(valueChanged(int)), this, SIGNAL (refreshInfo(int));
- 严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
- 在使用信号与槽的类中,必须在类的定义中加入宏 Q_OBJECT。
- 当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。