Qt信号和槽

简介

信号和槽是用于对象之间的通信,signals用来定义信号,emit用来发射信号。slots 用来定义槽函数;

自定义一个类想使用信号和槽,必须满足两点:

  • 继承自QObject
  • 添加 Q_OBJECT

槽函数限制:
1.槽函数的参数个数必须小于或等于信号参数,大于则报错;
2.槽函数参数类型必须和信号参数匹配;

Qt5写法:

QPushButton *pBtn1 = new QPushButton("测试按钮",this); 
connect(pBtn1,&QPushButton::clicked,this,&MainWindow::testFunc);
//槽函数
void MainWindow::testFunc()
{
    QPushButton *btn = qobject_cast<QPushButton*>(sender());//通过sender()获取信号发送者
    QString strName = btn->text();
}

Qt4写法

connect(pBtn1,SIGNAL(clicked()),this,SLOT(testFunc()));

有重载信号或重载槽函数时

Qt4写法简单,Qt5更安全、它会编译时对参数进行检测;

class Teacher : public QObject
{
    Q_OBJECT
public:
    explicit Teacher(QObject *parent = nullptr);
    void shitHungrySignal();

signals:
    void hungry(); //自定义信号,返回值为void,不需要实现,可以有函数,可以重载
    void hungry(QString foodName);//重载

};

class Student : public QObject
{
    Q_OBJECT
public:
    explicit Student(QObject *parent = nullptr);

public slots:
    void treat();//返回值必须void,需要实现,可以有参数,也可以重载
    void treat(QString foodName);//重载
};

调用:

this->st = new Student(this);
this->te = new Teacher(this);

//Qt4写法,可以通过参数来区分
connect(te,SIGNAL(hungry()),st,SLOT(treat()));
connect(te,SIGNAL(hungry(QString)),st,SLOT(treat(QString));

//Qt5写法,因无法区分重载信号和槽,需要使用函数指针方式
void(Teacher:: *theacherSignal1)(QString) = &Teacher::hungry;
void(Student:: *studentSignal1)(QString) = &Student::treat;
void(Teacher:: *theacherSignal2)(void) = &Teacher::hungry;
void(Student:: *studentSignal2)(void) = &Student::treat;
connect(te,theacherSignal1,st,studentSignal1);
connect(te,theacherSignal2,st,studentSignal2);

//Qt4问题,编译通过,但运行时无法触发槽函数,原因参数不匹配
connect(te,SIGNAL(hungry()),st,SLOT(treat(QString)));
//Qt5则会编译时直接报错,提示错误信息
connect(te,theacherSignal2,st,studentSignal1);

connect()的第五个参数

static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);
  1. Qt::AutoConnection //自动,这是默认参数,它会基于当前连接的状态自动选择直接还是队列,所以我们一般不需要填写第五个参数;
  2. Qt::DirectConnection //直接,槽函数所在的线程和发送者一样,单线程
  3. Qt::QueuedConnection //队列,槽函数所在的线程和接收者一样,多线程
  4. Qt::BlockingQueuedConnection//阻塞队列,槽函数所在线程和接收者一样,当槽函数执行完毕后发送者继续向下执行;(只能用于多线程中)
//直接
QPushButton *btn1 = new QPushButton(this);
connect(btn1,&QPushButton::clicked,this,&Widget::btn1Clicked);//第五个参数是Qt::DirectConnection

//队列
MyThread *thread = new MyThread(this);//自定义的线程类
connect(btn1,&QPushButton::clicked,thread,&MyThread::startThread);//Qt::QueuedConnection
thread->start();

//阻塞队列
MyThread *thread = new MyThread(this);
connect(thread,&MyThread::sig_progress,this,&Widget::slot_pp,Qt::BlockingQueuedConnection);
thread->start();

Lambda表达式

C++11新特性,如果QT版本低于5.4则需要在.pro文件中添加 CONFIG += c++11,但一般会自动生成;

[](){}; //这就是一个表达式
[=] //=号表示获取当前局部中的所有变量,值传递,是一个拷贝,无法修改真实变量
[&] //引用传递方式,操作的都是真实变量
() //参数
[](){}();//最后的()就是直接调用这个表达式

//ep.1-err
QPushButton *but1 = new QPushButton(this);

[](){
    but1->setText("aaa");//失败,找不到but1,需要使用=号来获取局部变量
};

//ep.1-ok
[=](){
    but1->setText("aaa");
};

//定义+调用
[=](){
    but1->setText("aaa");//失败,找不到but1,需要使用=号来获取局部变量
}();

//ep.2
QPushButton *myBtn = new QPushButton(this);
QPushButton *myBtn2 = new QPushButton(this);
myBtn2->move(100,100);
int m = 100;
connect(myBtn,&QPushButton::clicked,this,[m]()mutable {m=100+10;qDebug()<<m;});
connect(myBtn2,&QPushButton::clicked,this,[=](){qDebug()<<m;});
qDebug()<<m;
//注意第一个connect触发后结果永远是110,它的m是外部m的一份拷贝.并不影响外部m的值
//注意第一个connect因为是值传递的方式,如果要修改值则需要添加mutable关键字,否则报错

//ep.3
QPushButton *but3 = new QPushButton(this);
but3->setText("关闭");
but3->move(100,0);
connect(but3,&QPushButton::clicked,this,&Widget::close());//可以
connect(but3,&QPushButton::clicked,this,[=](){
    //可以在这里坐一起其他操作再关闭窗口
    //...
    this->close();
});

绑定自定义类型

当我们使用信号和槽来传递一些自定义类型数据时,通常会报如下错误:

//自定义数据
class MyClass
{
public:
	MyClass();
	int m_nAge;
	QString m_strNmae;
};
//调用
MyThread *thread = new MyThread(this);
connect(thread,SIGNAL(sig_message(MyClass)),this,SLOT(slot_message(MyClass)));
thread->start();
//报错
QObject::connect: Cannot queue arguments of type 'MyClass' 

以上错误表示:Qt对该类型未知,需要提前注册到元对象系统中来告诉Qt;
添加如下代码即可:

  1. 在自定义类型文件顶部添加
  2. 在自定义类型尾部添加声明 Q_DECLARE_METATYPE(XXXX)
#include 
class MyClass
{
public:
	MyClass();
	int m_nAge;
	QString m_strNmae;
};
Q_DECLARE_METATYPE(MyClass)

信号槽在多线程中使用自定义类型

上面所说是Qt::DirectConnection的单线程连接模式时的使用方法,但如果是多线程Qt::QueuedConnection等建立的连接时,还需添加一步:
3.通过qRegisterMetaType<>()来注册类型,来确保在第一次建立connect之前类型被注册; ( 注意:在哪绑定信号槽就在哪注册;比如我要在主界面中绑定与子线程中的自定义类型,可以在主界面的构造函数中提前注册好MyClass类型; )

mainWidget::mainWidget(QWidget *parent) :QWidget(parent),ui(new Ui::mainWidget)
{
    ui->setupUi(this);
    //注册
    qRegisterMetaType<MyClass>("MyClass");
}

你可能感兴趣的:(Qt,qt)