Qt-信号/槽(single/slot)机制
2015年1月6日
2015年3月28日添加信号映射
2015年3月29日添加断开连接
在信号/槽机制之前,一般使用回调函数进行交互。但是这种方式有两个基本的问题:一是类型安全问题,函数对象无明确类型。二是高度耦合,回调函数必须被明确包含在处理函数中。
信号和槽进行连接后,调用信号就会调用槽。由于信号和槽都是成员函数,是类型安全的。默认情况下也是线程安全的,如果指定了类型,可能降级为类型安全。
信号和槽是完全无关的,使用connect连接才能建立联系,并且槽函数可以忽略信号的参数,两者之间是松散耦合的。
因此,在使用时要注意信号-槽之间的解耦。
在执行时,qmake会调用MOC(metaObject compiler)工具,将QObject相关的类重新生成一个新的类MOC_xxx.cpp/h,这时,将所有信号转换为相应的回调函数(槽函数),发出信号就会调用槽函数(参见QObject.cpp->QMetaObject::activate())。
参考 http://woboq.com/blog/how-qt-signals-slots-work.html
http://stackoverflow.com/questions/14080484/qt-signals-and-slot-thread-safety
信号与槽技术是Troll Tech公司独立创建的技术。使用MOC(meta object compiler)创建合乎C++标准的回调函数。
信号是特殊的成员函数,只有声明无实体。emit关键字是可选标记。signal/slot也是标记。
Q_OBJECT也是用于标记的宏,只有声明此宏,才会进行信号槽的解析。
signal信号:代表事件,包含用户事件,内部状态事件。使用成员函数实现。
发射信号:emit(可选)。
slot槽:处理事件。标准函数+添加slot标志。
connect连接:将signal与slot进行connect,这样在emitsignal时就调用slot。
QSignalMapper实现信号-槽的连接,只能int,const QString&,QObject *,QWidget*可以作为参数。
注意:只有继承QObject,并且在类的起始声明Q_OBJECT宏,才能使用信号/槽。
方法:
注意:连接时,只能使用它们的签名,而不应该带有参数名,否则也会出错。
注意:如果连接到当前的槽,则可能省略receiver,但并不推荐这么做,会引起混淆。
槽函数可以接收信号传递的参数,也可接收少于信号的参数;
连接:connect(signalObject,signal,slotObject,slot);
断开连接:disconnect(singalObject,signal,slotObject,slot);
连接:connect(signalObject,signal,signal,signal);
可以自定义连接时动作:connectNotify(),disconnectNotify()
QLatin1String 和 SIGNAL()可以判断信号类型。
重写此虚函数,可以指定在连接某个信号时的动作。
示例:
if (QLatin1String(signal) == SIGNAL(valueChanged(int))) {
// signal isvalueChanged(int)
}
MOC具有自动连接机制,可以根据指定的命名方式,将ui文件中控件的信号和槽进行连接。因此,只有在setupUI()函数之前定义的具有ObjectName的对象,才有可能被自动连接。
注意:自动连接只能应用于QObject与其子对象之间。
自动连接命令方式:void on_<object name>_<signalname>(<signal parameters>);
MOC在ui文件的setupUi()函数中使用QMetaObject::connectSlotsByName(this)函数完成这一功能。
示例:
以下槽函数将自动连接到okButton的clicked信号上。
private slots:
voidon_okButton_clicked();
如果信号或槽的对象释放,则此连接自动删除。
手动创建部件必须在setupUI()函数执行之前定义,并且设置ObjectName,才能被MOC进行自动连接。注意:创建完成后使用setParent()才能将对象放入指定的父控件。
示例:
QPushButton *pMyBtn = new QPushButton(this);
pMyBtn->setText ("this is my btn");
pMyBtn->setObjectName ("pushButton_8");
pMyBtn->show ();
ui->setupUi(this);
pMyBtn->setParent (ui->centralWidget);
可以获取QObject* 类型的发送对象;将获取的指针进行相应的转化,就可以得知发送对象的详细信息,如whatsThis(),objectName()...;
示例:
qDebug()<<((QPushButton*)(sender()))->objectName();
用于获取信号的接收者数目。注意:是protected成员。
示例:
connect(this,SIGNAL(transRoutePoint(const QString &,const QString &, const QString &)),this,SLOT(zoomInMap()));
receivers(SIGNAL(transRoutePoint(const QString &, constQString &, const QString &)));//==1;
如果block==true;那么由这个对象发送的信号不会引起任何响应(destroyed()信号除外);
bool QObject::signalsBlocked () const
可以获取当前对象的信号阻止状态;
示例:
m_pPlaneAirline->blockSignals(true);//m_pPlaneAirline发送的信号不会被响应
qDebug()<<"signalcount:"<<m_pPlaneAirline->signalsBlocked();//返回true
多数类都是可以重入的,但不是线程安全的。因此,QT类都应当只在一个线程中使用。因为每个线程都有自己的事件循环。
在多线程中,槽函数的执行是在发送信号的线程中还是在执行槽函数的线程中有完全不同的表现。因此QT给出了连接选项,以指定执行线程。
Qt::DirectConnection,表示直接连接,发送信号后直接执行槽函数,在发送信号线程执行。
Qt::QueuedConnection,表示顺序连接,发送信号后送入队列,只有回到槽函数所在的线程后,再顺序执行。只有将控制权转换到响应线程时才会作出响应,也就是说,如果发送信号线程一直工作,就无法将控制权转换到响应线程。只有发送线程sleep()之后,响应线程才有可能(但不一定)得到控制权。
Qt::BlockingQueuedConnection:与QueuedConnection相似,但每次信号执行会阻塞当前线程,等待slot返回。有可能导致死锁。
Qt::AutoConnection:默认值,单线程时,使用DirectConnection;多线程时,使用QueuedConnection。
Qt::UniqueConnection:与AutoConnection相似,但会清除重复的连接(对于一个线程调用另一个线程中的对象发送信号这种错误应用的容错处理)。
Qt::AutoCompatConnection:兼容QT3。
因此,如果发送者和接收者不在同一个线程,则直接连接是不安全的。
调用其它线程中对象的函数,都是不安全的。
每一个信号的对象,在发出时所在的线程,会有一个信号对象放入信号队列。在多个线程中,使用同一个对象,每个线程中保留源对象的一个副本,然后线程返回主线程时,发送一次信号,则每个线程实际发送了所有线程的信号。
示例:
// DirectConnection,直接执行,所以执行是连贯的
Debugging starts
1 f isStarted? true
sum is running... 1
1 runing...
1 task finished.
sum is finished. 1
2 f isStarted? true
sum is running... 2
2 runing...
2 task finished.
2 task finished.
sum is running... 3
3 f isStarted? true
sum is finished. 2
3 task finished.
3 runing...
3 task finished.
3 task finished.
sum is finished. 3
Debugging has finished
// QueuedConnection,接收者线程执行,所以执行是不连贯的
Debugging starts
1 f isStarted? true
sum is running... 1
1 runing...
sum is finished. 1
2 f isStarted? true
2 runing...
sum is running... 2
sum is finished. 2
sum is running... 3
3 f isStarted? true
3 runing...
sum is finished. 3
1 task finished.
2 task finished.
2 task finished.
2 task finished.
3 task finished.
3 task finished.
3 task finished.
Debugging has finished
目标:槽函数读取信号发送方的信息。
方法:信号映射。
不能在槽函数的执行线程中需要读取发送线程的对象信息,这是不安全的。如果发送对象已经释放,将无法读取数据。
如果信号是无参数的,可以使用QSignalMapper映射信号,将相关数据发送。参见:信号映射:QSignalMapper。
如果信号是有参数的,应该构造相关的映射类发送数据。
示例:将QProcess的信号映射为带有ID和相应信息的信号
//mapper定义
class QProcessMapper:publicQObject
{
Q_OBJECT
public:
QProcessMapper(const QString&strID,QObject *parent=NULL):
QObject(parent),
m_strID(strID)
{}
signals:
void started(const QString &strID);
void finished(const QString &strID,intiExitCode);
void readyReadStandardOutput(const QString&strID,const QString &strRst);
public slots:
void handleStarted(){emit started(m_strID);}
void handleFinished(int iExitCode){emitfinished (m_strID,iExitCode);}
void handleReadyRead(){emitreadyReadStandardOutput (m_strID,((QProcess*)sender())->readAll ());}
private:
QString m_strID;
};
//mapper 映射
QProcessMapper mapper(strID);
connect(&mapper,SIGNAL(started(QString)),pHandler,SLOT(handleStarted(QString)));
connect(&mapper,SIGNAL(finished(QString,int)),pHandler,SLOT(handleFinished(QString,int)));
connect(&mapper,SIGNAL(readyReadStandardOutput(QString,QString)),pHandler,SLOT(handleReadyRead(QString,QString)));
QProcess process;
connect(&process,SIGNAL(started()),&mapper,SLOT(handleStarted()));
connect(&process,SIGNAL(finished(int)),&mapper,SLOT(handleFinished(int)));
connect(&process,SIGNAL(readyReadStandardOutput()),&mapper,SLOT(handleReadyRead()));
参见:QT特性-QObjectMOS元对象系统SignalSlot信号槽.docx事件系统部分。
目标:将多个无参数的信号连接映射器,由映射器指定参数并重新发送。
方法:QSignalMapper
连接映射器:将指定的信号连接到映射器的map()槽。
指定参数:setMapping()。
使用映射器:将映射器的mapped()信号与需要的槽函数连接。
示例:
signalMapper= new QSignalMapper(this);
connect(button,SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(button,QString(”myButton”));
connect(signalMapper, SIGNAL(mapped(QString)),this, SIGNAL(clicked(QString)));