信号槽是Qt中最重要的机制,现将信号槽的基本用法总结如下。
信号槽类似于软件设计模式中的观察者模式,(观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。)被观察者发出的信号(signal),观察者收到自己注册监听signal,就通过槽(slot)关联的槽函数function实现动作操作。
信号槽优点:
(1)类型安全
信号的参数类型、参数个数需要和槽函数的参数类型和参数个数一致。槽函数的个数可以少于信号的参数个数,但缺少的参数必须是信号参数的最后一个或最后几个。
(2)松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject。
(3)效率
信号槽增强了对象间通信的灵活性,同时损失了一些性能,通过信号调用的槽函数比直接调用速度慢约10倍(因为需要定位信号接收者;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是满足绝大部分场景。
注:
信号:没有访问权限修饰符,是公有的,没有函数体定义,返回类型是 void,信号在 moc 自动产生。
槽函数:需要有访问权限修饰:public,private,protected。
通过connect关联信号和槽函数
(1)Qt5以前的connect有以下几种:
bool connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
bool connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
bool connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const
Qt4使用了SIGNAL和SLOT这两个宏,将信号和槽的函数名转换成了字符串。注意,不能将全局函数或者 Lambda 表达式传入connect()。使用字符串导致了Qt4有以下缺点:一旦出现连接不成功的情况,Qt 4 是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
第一种是比较常用的,可简单描述如下:
connect(obj1, SIGNAL(fun1(param1, param2,...)), obj2, SLOT(fun2(param1,...)));
优点:对所有控件都适用。
缺点:书写繁琐,槽函数必须在slot标签下。在程序编译阶段,程序会将函数以字符串的形式进行链接,程序不会检查信号/槽函数是否存在,只有在运行期间才会验证是否正确
(2)Qt5写法有以下几种
QMetaObject::Connection connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const;
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);
相对Qt5以前,第四种和第五种是新增的。
第四种比较常用,可简单描述如下:
connect(obj1, &ClassA::fun1, obj2, &ClassB::fun2);
优点:书写简便,编译期间就会检查信号与槽是否存在,参数类型检查,Q_OBJECT是否存在,槽函数不在限定必须是slot,可以是普通的函数、类的普通成员函数、lambda函数。
缺点:函数重载,有可能会造成程序的困扰,不知道该具体链接哪个
解决:a)可以使用Qt5以前的写法。
b)或者使用函数指针,eg:
connect(comboBox, static_cast(&QComboBox::activated),
[=](int index){ /* ... */ });
关于connect几种函数的详细介绍可参考Qt的信号槽机制介绍(含Qt5与Qt4的差异对比)_花园王国---Garden kingdom-CSDN博客
(1)一个信号可以关联多个槽函数。
connect(obj1,SIGNAL(signal1),obj1,SLOT(fun1()));
connect(obj1,SIGNAL(signal1),obj1,SIGNAL(fun2()));
connect(obj1,SIGNAL(signal1),obj1,SIGNAL(fun3()));
注:Qt5以后,支持按照建立连接的顺序触发槽函数。即如果一个信号连接到多个槽,则在发出信号时,这些槽的激活顺序与建立连接的顺序相同。If a signal is connected to several slots, the slots are activated in the same order in which the connections were made, when the signal is emitted.
Qt5以前:信号发射时,与之相关联的槽的执行顺序将是随机的,且顺序不能指定。执行结果:
(2)多个信号可以关联一个槽函数。
connect(obj1,SIGNAL(signal1),obj1,SLOT(fun1()));
connect(obj2,SIGNAL(signal2),obj1,SIGNAL(fun1()));
connect(obj3,SIGNAL(signal3),obj1,SIGNAL(fun1()));
//槽函数可以根据sender()->objectName()来判断是哪个信号触发的槽函数
(3)一个信号可以关联另一个信号。
connect(obj1,SIGNAL(signal1),obj2,SIGNAL(signal2));
(4)一个信号关联多个信号
connect(obj1,SIGNAL(signal1),obj2,SIGNAL(signal21));
connect(obj1,SIGNAL(signal1),obj2,SIGNAL(signal22));
connect(obj1,SIGNAL(signal1),obj2,SIGNAL(signal23));
demo如下:
///.h///
#pragma once
#include
#include "ui_SignalSlot.h"
#include
class SignalSlot : public QWidget
{
Q_OBJECT
public:
SignalSlot(QWidget *parent = Q_NULLPTR);
public slots:
//响应btn1
void OnBtn1();
void OnSetTextStyle();
void OnSetWindowTitile();
//响应自定义信号
void OnSignalMsg();
//响应btn2 clicked
void OnBtn2Signal();
//响应自定义信号
void OnCustomSignal(int x,int y);
//自定义信号
signals:
void signalMsg1();
void signalMsg2();
void signalMsg3();
void signalMsg(int x,int y);
private:
Ui::SignalSlotClass ui;
QTimer *m_pTimer;
};
.cpp
#include "SignalSlot.h"
#include
#include
SignalSlot::SignalSlot(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
///一个信号关联多个槽函数 start//
connect(ui.pbtn1,SIGNAL(clicked()),this,SLOT(OnBtn1()));
connect(ui.pbtn1, SIGNAL(clicked()), this, SLOT(OnSetTextStyle()));
connect(ui.pbtn1, SIGNAL(clicked()), this, SLOT(OnSetWindowTitile()));
/一个信号关联多个槽函数 end
///多个信号关联一个槽函数 start//
connect(this, SIGNAL(signalMsg1()), this, SLOT(OnSignalMsg()()));
connect(this, SIGNAL(signalMsg2()), this, SLOT(OnSignalMsg()()));
connect(this, SIGNAL(signalMsg3()), this, SLOT(OnSignalMsg()()));
///多个信号关联一个槽函数 end//
///一个信号关联多个信号 start//
///一个信号关联一个信号 start//
connect(this, SIGNAL(signalMsg3()), ui.pbtn2, SIGNAL(clicked()));
connect(ui.pbtn2, SIGNAL(clicked()), this, SLOT(OnBtn2Signal()));
///一个信号关联一个信号 end//
connect(this, SIGNAL(signalMsg3()), ui.pbtn2, SIGNAL(pressed()));
connect(ui.pbtn2, &QPushButton::clicked, this,&SignalSlot::OnBtn2Signal);
///一个信号关联多个信号 end//
///带参信号槽/
connect(this, SIGNAL(signalMsg(int,int)), this, SLOT(OnCustomSignal(int,int)));
///信号槽lambda写法/
m_pTimer = new QTimer(this);
connect(m_pTimer, &QTimer::timeout, [=]() {
ui.lineEdit3->setText(QString::fromLocal8Bit("你好,中国"));
ui.lineEdit4->setText(QString::fromLocal8Bit("你好,中国"));
qDebug() << "11111" << endl;
m_pTimer->stop();
});
m_pTimer->start(1000*10);//启动定时器
}
void SignalSlot::OnBtn1()
{
ui.lineEdit1->setText(QString::fromLocal8Bit("你好,世界!"));
qDebug() << "OnBtn1" <setStyleSheet(" QLineEdit{ background: yellow }");
qDebug() << "OnSetTextStyle" << endl;
}
void SignalSlot::OnSetWindowTitile()
{
this->setWindowTitle(QString::fromLocal8Bit("测试窗口"));
qDebug() << "OnSetWindowTitile" << endl;
emit signalMsg1();
emit signalMsg2();
emit signalMsg3();
}
void SignalSlot::OnSignalMsg()
{
qDebug() << sender()->objectName() << endl;
}
void SignalSlot::OnBtn2Signal()
{
ui.lineEdit2->setText(QString::fromLocal8Bit("你好,中国"));
qDebug() << "OnBtn2Signal"<< endl;
}
void SignalSlot::OnCustomSignal(int x, int y)
{
qDebug() << "x" << x << ",y" << y << endl;
}
执行结果
备注:
lambda用法可参考:C++中的Lambda表达式详解_NickWei的博客-CSDN博客_lambda表达式c++
lambda函数定义如下:
[capture](parameters) mutable ->return-type{statement}
(1)[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;
(2)(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;
(3)mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);
(4)->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;
(5){statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:
(1)[var]表示值传递方式捕捉变量var;
(2)[=]表示值传递方式捕捉所有父作用域的变量(包括this);
(3)[&var]表示引用传递捕捉变量var;
(4)[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
(5)[this]表示值传递方式捕捉当前的this指针。