QT5信号槽个人总结
一、关于qt信号槽的理解
这里沿用豆子大神的话说,所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
备注:强烈推荐豆子大神的Qt学习之路
提示:以下个人浅见,讲的过于直白,大佬绕道。
二、Qt5信号槽基本使用
在Qt5中,QObject::connect()有五个重载:
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);
对五个重载进行分析:
1.函数返回值都是QMetaObject::Connection类型,这个返回值一般很少有用到,关于返回值的意义qt官方文档的说明是:
Returns a handle to the connection that can be used to disconnect it later.
返回该连接的句柄,以后可用于断开连接。也就是说我们可以手动断开这个connect,我们绑定widget的Signal_DoSomeThing信号到widget的slot_toDo槽,然后当槽函数执行完成后手动断开连接,伪代码如下:
auto connection = QObject::connect(this, &MyWidget::Signal_DoSomeThing, this, &MyWidget::slot_toDo);//auto:自动类型推断,见c++11特性
emit Signal_DoSomeThing();
disconnect(connection ); //断开连接的时候再发出信号无法再触发槽函数
显然一般情况下我们不会这么做,这种应用场景一般结合c++11的lambda 表达式应用,我们将上句代码进行变形:
void func1()
{
int para = 1;
auto connection = QObject::connect(this, &MyWidget::Signal_DoSomeThing, [this, ¶](){
//ToDo
});
emit Signal_DoSomeThing();
disconnect(connection ); //断开连接的时候再发出信号无法再触发槽函数
}
void func2()
{
QString para = "a";
auto connection = QObject::connect(this, &MyWidget::Signal_DoSomeThing, [this, ¶](){
//ToDo
});
emit Signal_DoSomeThing();
disconnect(connection ); //断开连接的时候再发出信号无法再触发槽函数
}
这样我们就可以根据需求灵活变更,使用完成后切断当前连接,代码耦合性更好。
似乎我们按照我们的想法实现了它该有的功能,但实际使用这根本不需要信号槽连接,直接调函数不就完了,显然不应该是这种连接完后马上就去发信号执行槽这种做法,而是由外部触发这个信号,那外部怎么触发这个信号呢,必然是函数内执行了某个操作,执行完毕后触发这个信号,然后再执行即时绑定的槽,而这个函数内执行的某个操作,必然是应用于多线程的,如果是单线程的,那也就直接调函数了,还需要信号槽吗。
梳理一下,我们的应用逻辑应该是绑定一个信号槽,然后执行一个线程操作,线程执行完毕后触发信号,执行即时绑定槽,执行完毕后断开信号槽连接。那这里就涉及到一个问题,我们需要等待槽函数执行之后再断开连接,槽函数执行又需要等待线程执行完毕触发信号后再执行,为了不影响主线程刷新,QEventLoop很好的解决了这个问题。
到这里就比较清晰了,举一个典型例子,我们在给服务器发消息的时候,希望即时收到回复并作出相应处理,伪代码如下:
void getMsgFromServerAndToDo(QString msg)
{
QString para = "a";
QEventLoop eventLoop;
auto connection = QObject::connect(this, &MyWidget::Signal_ResieveMsg, [this, ¶, &eventLoop](const QString &msgResult){
//ToDo
...
eventLoop.quit(); //退出事件循环
});
sendMsg(msg); //发送消息后进入事件循环,等待接收到消息后触发Signal_ResieveMsg
eventLoop.exec();
disconnect(c);
}
结合这个例子来看,是不是这种设计就非常巧妙,那这个para有什么用?当然是为接收到的数据处理用啊,比如你要比对本地与服务器数据,比如你要结合服务器数据存储到数据结构。
2.信号槽的连接参数中,sender 和receiver 类型是const QObject *,只有signal 和slot的类型不一样,
分析第一个,signal和slot的类型的类型都是const char *,Qt4中也包含了同样的重载,因此,这种方式Qt4和Qt5是兼容的:
QObject::connect(button, SIGNAL(clicked()),
this, SLOT(slot_toDo()));
SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串,信号加前缀2,槽加前缀1。它与以下代码是等效的:
connect(button,”2clicked()”, this, ”1slot_toDo()”);
分析第二种,signal和slot的类型的类型都是const QMetaMethod &,我们可以将每个函数看做是QMetaMethod的子类。因此,这种写法可以使用QMetaMethod进行类型比对。
分析第三种,缺少了 receiver。这个函数其实是将 this 指针作为 receiver。
分析第四种,signal 和 slot 类型则是PointerToMemberFunction。看这个名字就应该知道,这是指向成员函数的指针,而Qt4无此重载,这也解释了为什么Q5可以使用如下形式而Qt4不可以:
QObject::connect(button, &QPushButton::clicked,
this, &Qwidget::slot_toDo);
分析第5种,最后一个参数是Functor类型,Functor类型是什么?也就是C++中的仿函数,至于什么是仿函数这里不作解释,而Functor类型,使得槽函数可以接受任何成员函数、static 函数、全局函数以及 Lambda 表达式。
######先写这么多…