天天看点

Qt 信号与槽1、信号与槽机制的连接方式2、信号与槽机制的优点3、信号与槽机制的效率4、信号映射器5、Qt元对象表机制与MFC消息映射机制

前言:

信号和槽用于两个对象之间的通信,信号和槽机制是Qt的核心特征。

为了实现对象间的通信,一些工具包中使用了回调(callback)机制,而在Qt中,使用了信号和槽来进行对象间的通信。

信号和槽的关联,可以是一个信号对应一个槽,一个信号可以关联到多个槽上,多个信号也可以关联到同一个槽上,甚至,一个信号还可以关联到另一个信号上。

如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地执行,但是它们执行的顺序是随机的,无法指定它们的执行顺序。

只有QObject类及其子类派生的类才能使用信号和槽机制,而且还必须在类声明的最开始处添加Q_OBJECT宏。

声明一个信号要使用signals关键字,在signals前面不能使用public、private和protected等限定符。只有定义该信号的类及其子类才可以发射该信号。而且信号只需要声明,不需要也不能对它进行定义实现。还要注意的是,信号没有返回值,只能是void类型的。

声明一个槽需要使用slots关键字。一个槽可以是private、public或者protected类型的,槽也可以被声明为虚函数,这与普通的成员函数是一样的,也可以像调用一个普通函数一样来调用槽。槽的最大特点就是可以和信号关联。

槽中的参数的类型要和信号的参数的类型相对应,且不能比信号的参数多。

1、信号与槽机制的连接方式

信号和槽进行关联,使用的是QObject类的connect()函数,这个函数的原型如下:
bool QObject::connect ( const QObject *sender, const char * signal, const QObject * receiver, const char * method,Qt::ConnectionType type = Qt::AutoConnection )
           
QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
           
参数说明
参数 说明
1、const QObject *sender 发送信号的对象
2、constchar * signal 要发送的信号
3、constQObject * receiver 接收信号的对象,默认是this,表明是本部件
4、constchar * method 要执行的槽
5、Qt::ConnectionTypetype = Qt::AutoConnection 关联的方式(同步/异步),默认AutoConnecttion
Qt::ConnectionType type
常量 描述

Qt::AutoConnection 自动方式

默认,可同步可异步

Qt的默认连接方式,假设信号的发出和接收这个信号的对象同属一个

线程,那个工作方式与直连方式相同。否则工作方式与排队方式相同。

连接类型是在发出信号时确定。

Qt::DirectConnection 直连方式

类似于函数调用,同步运行

1

当信号发出后。对应的槽函数将马上被调用。

emit语句后的代码将在全部槽函数运行完成后被运行

Qt::QueuedConnection 排队方式

类似于消息通信,异步运行

2

当信号发出后。排队到信号队列中,需等到接收对象所属线程的

事件循环取得控制权时才取得该信号,然后调用对应的槽函数。

emit语句后的代码将在发出信号后马上被运行。无需等待槽函数运行完成

Qt::BlockingQueuedConnection

信号和槽必须在不同的线程中。

否则就产生死锁

3

这个是全然同步队列仅仅有槽线程运行完毕才会返回。

否则发送线程也会一直等待,相当于是不同的线程能够同步起来运行

Qt::UniqueConnection 0x80

与默认工作方式同样。仅仅是不能反复连接同样的信号和槽。

由于假设反复连接就会导致一个信号发出。相应槽函数就会运行多次

Qt::AutoCompatConnection

Qt5已舍弃

4

是为了连接Qt4与Qt3的信号槽机制兼容方式。

工作方式与Qt::AutoConnection一样

对于信号和槽的参数问题,基本原则是信号中的参数类型要和槽中的参数类型相对应,而且信号中的参数可以多于槽中的参数,但是不能反过来,如果信号中有多余的参数,那么它们将被忽略。

利用Qt::QueuedConnection可以实现异步调用:

对于一个多线程GUI程序来说,经常遇到的需求是后台有比较复杂的IO、网络、数据处理逻辑,响应的业务逻辑有多个状态,并常伴有延时。这种情况下用多线程处理比用定时器方便。而一旦使用多线程,就涉及到与GUI主线程的同步问题。 

从开发者的角度,希望使用的是“线程透明”的交互机制,即直接跨线程修改GUI控件属性;GUI中用户的输入事件传递到数据处理线程中,并在适当时被响应和处理。 

我们希望图形库提供以上封装好的方法,不需要开发者手动处理互斥锁、消息队列等。 Qt做到了。

信号和槽还有一种自动关联方式,例如前面程序中在设计模式直接生成的按钮的单击信号的槽,就是使用的这种方式:on_pushButton_clicked(),它由“on”、部件的objectName和信号三部分组成,中间用下划线隔开。这样组织的名称的槽就可以直接和信号关联,而不用再使用connect()函数。

2、信号与槽机制的优点

1 > 类型安全。

需要关联的信号和槽的签名必须是等同。

即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数必须是信号参数的最后一个或者几个参数。

2 > 松散耦合。

信号和槽机制减弱了Qt对象的耦合度。

激发信号的Qt对象无须知道是哪个对象的哪个槽需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收到了信号。

同样地,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除。应用程序也不会崩溃

3、信号与槽机制的效率

信号和槽机制增强了对象间通信的灵活性,然而这也损失了一些性能。同回调函数相比,信号和槽机制运行速度有些慢。通常,通过传递一个信号来调用槽函数将会比直接调用直接调用非虚函数运行速度慢10倍。原因如下:

1)需要定位接收信号的对象。

2)安全地遍历所有的关联。

3)编组/解组传递的参数。

4)多线程的时候。信号可能需要排队等待

然而,与创建堆对象的new操作及删除堆对象的delete操作相比,信号与槽的运行代价只是它们很少的一部分。信号与槽机制导致的这点性能损耗,对实时应用程序是可以忽略的;同信号与槽提供的灵活性和简便性相比,这点性能的损失也是值得的。

4、信号映射器

1  > 在QT编程中,如果遇到多个控件,但是他们的槽大致相同,这个该怎么处理,比如10个button,难道要写10个槽,可以不必这样,QSignalMapper信号映射器可以帮组我们解决。

① QSignalMapper类通过自定义信号发送者来捆绑信号。 

② QSignalMapper 类收集一组无参信号,然后以 integer,string或者widget为参数重新发送信号,相当于经过QSignalMapper转发后,发送无参信号的类发送的有参的信号。

③ QSignalMapper 类通过setMapping(),支持从特定的字符串或者整数映射到指定的类(指QPushButton及其他)的功能。然后,这些指定的类将被连接到map()槽上,这个槽将发送 mapped() 后的和原始的发信号的类的关联的带有字符串或者整数的信号。映射的关系随后通过removeMappings()可以被移除。

简单说,就是所有指定的类,譬如按钮,都和QSignalMapper的map(),建立连接,然后由QSignalMapper类负责发送不同指定对象映射的不同的信号

2 > 样例

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QSignalMapper>
class MainWidget : public QWidget
{
   Q_OBJECT 
//槽函数
private slots: 
    //处理最终信号的槽
    void doClicked(const QString & btnname);
private:
   QSignalMapper *signalMapper;
public:
   MainWidget(QWidget *parent = 0);
};
#endif // MAINWIDGET_H 
           
mainwidget.cpp
#include <QGridLayout>
#include <QPushButton>
#include <QMessageBox>
#include "mainwidget.h"
void MainWidget::doClicked(const QString &btnname)
{
   //显示被按下的btn名称。 
   QMessageBox::information(this, "Clicked", btnname + " is clicked!");
}
MainWidget::MainWidget(QWidget *parent) : QWidget(parent)
{
   //10个button 
   QString buttontext = "btn1,btn2,btn3,btn4,btn5,btn6,btn7,btn8,btn9,btn10"; 
   QStringList texts = buttontext.split(","); 
   signalMapper = new QSignalMapper(this); 
   QGridLayout *gridLayout = new QGridLayout; 
   for (int i = 0; i < texts.size(); ++i) 
   {
      QPushButton *button = new QPushButton(texts[i], this); 
      button->setObjectName(texts[i]); 
      //原始信号传递给signalmapper 
      connect(button, SIGNAL(clicked()), signalMapper, SLOT(map())); 
      signalMapper->setMapping(button, texts[i]); 
      //设置signalmapper的转发规则, 转发为参数为QString类型的信号, 并把texts[i]的内容作为实参传递。 
      gridLayout->addWidget(button, i / 3, i % 3); 
   }
   //将转发的信号连接到最终的槽函数
   connect(signalMapper, SIGNAL(mapped(const QString &)), this, SLOT(doClicked(const QString &))); 
   setLayout(gridLayout);
}
           
main.cpp
#include "mainwidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{ 
   QApplication a(argc, argv); 
   MainWidget w;
   w.show(); 
   return a.exec();
}
           

从这个例子来看QSignalMapper的用法是非常简单的, 也很容易理解。  

A. 首先把原始不带参数的信号连接到signalmapper的map()槽函数, 这样signalmapper能在第一时间接收到原始信号;  

B. 其次调用setMapper方法告诉signalmapper怎样去处理原始信号。 在这个例子中是把原始信号转化为一个带QString参数的信号 

C. 最后接收转化后的带参数信号, 这里所把转化后的信号与槽函数连接, 在槽函数中获得需要的数据

3  > 小结

① 所有信号都和 QSignalMapper 的 map()槽关联;

② 通过setMapping()建立,自定义对象和信号之间的关系(对应一个整数、字符串或者QWidget);

③ 最后通过将 QSignalMapper 的 mapped(),和自定义的带参的函数相关联。

5、Qt元对象表机制与MFC消息映射机制

MFC:消息映射机制,Qt:元对象表机制。

在Qt中,元对象表机制最大的作用就是支持信号槽机制。

任何从QObject派生的类都包含了自己的元数据模型,一般通过宏Q_OBJECT定义的。

QMetaObject类型的静态成员变量,就是元数据的数据结构。

MFC在类中实现消息映射机制需要4个宏,一个用于声明,三个用于定义

DECLARE_MESSAGE_MAP();//声明消息映射表的宏


//定义消息映射表的实现的3个宏
BEGIN_MESSAGE_MAP(theClass,baseClass)
ON_COMMAND(id,memberFxn)
defineEND_MESSAGE_MAP()
           
可以看到,实现的3个宏中,需要涉及到该类的基类,而Qt在使用时,只需继承QOBject和添上一个宏Q_OBJECT。

其他:

Qt 信号与槽

继续阅读