Qt信号与槽机制

发布时间:




Qt信号与槽}







一、信号与槽机制
1.概述
GUI用户界面中,当用户操作一个窗口部件时,需要其他窗口部件的响应或者能够激活其他的操作。在程序开发中,经常使用回调(callback机制来实现。所谓回调,就是事先将一个回调函数(callbackfuncation指针传递给某一个处理过程,当这个处理过程得到执行时,回调预先定义好的回调函数以期实现激活其他处理过程的目的。
不同与回调函数机制,Qt提供了信号与槽机制。信号和槽机制是QT的核心机制,要精通QT编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是QT的核心特性,也是QT区别于其它工具包的重要地方。
当某个对象的状态发生变化时,该对象会触发一个信号。这信号与另外一些对象的槽函数绑定,信号的触发将导致执行这些槽函数,这些槽函数进行对象状态变化的特殊处理,而完成对象之间的通信。
本节将阐述Qt为什么使用信号与槽机制而不是传统的回调函数机制进行对象间的通信,信号与槽的具体的分析,和如何定义信号与槽及其绑定。
2.动机
给定一个类,它和其他类有两种交互方式:1在该类的设计阶段即可确定和哪个类交互。这种情况下,我们只需要简单地调用目标类的成员函数,即可完成交互。2)在该类的设计阶段无法确定和哪个类交互。下图1-1QtCreator高级查找功能对话框。用户在“查找”之后的行编辑框输入一个带搜索的字符串,在该编辑框下面的几个选择框指定搜索条件。用户按“搜索”按钮后,QtCreator在用户正编辑的文档中搜索指定的字符串。只有当编辑框中的内容不为空时,“搜索”按钮才被使能,否则,该按钮被禁用。

1-1QtCreator高级查找功能对话框

Qt使用类QLineEdit实现图中编辑框的功能。假如我们是该类的设计者,我们必须解决一个问题:虽然在这个具体例子中,我们确切地知道是“搜索”按钮关注编辑框的内容是否为空,但是在QLineEdit的设计阶段,我们根本无法预测哪些类将会关注编辑框的数据状态。因此,当编辑框的状态在“空”和“不空”之间切换时,我们不知道将这种状态变化通知给哪个对象。
一种可能的做法是使用回调函数(callbackfunction。在设计QLineEdit时,我们令其



存放一个函数指针。使用该类时,我们令该指针指向一个处理上述状态变化的函数,该函数被称为回调函数。在该例子中,我们应该令其指针指向“搜索”按钮的某个成员函数。当QLineEdit的状态发生变化时,这个成员函数会被执行,完成QLineEdit对象和“搜索”按钮对象之间的交互。
这种做法不灵活。如果有多个类都在关注某个类的状态变化,存放一个回调函数的信息显然无法满足要求,此时需要维护一张表,存放多个回调函数的地址。对于每一个被关注的类都需要做类似的工作,因而这种做法效率低、不灵活。
Qt使用信号与槽机制来解决这个问题。假设类A的状态发生变化时,需要通知对象B以执行类B的成员函数slot(来处理这种状态的变化。
所谓信号,就是类A定义的一个成员函数,比如signal(,当A的状态发生变化时,对A将状态信息封装在该函数的参数中,调用该函数。
所谓槽,也是类B的一个成员函数,比如上述的slot(
通过Qt提供的QObject::connect(函数,可以将一个信号函数与槽函数绑定,当信号函数被调用时,与其绑定的槽函数就会被调用。
这种处理方式高效、灵活。Qt有一套专门的机制来处理信号与槽,开发人员在软件设计阶段只需要指定一个类含有哪些信号函数、哪些槽函数,Qt会来处理信号函数和槽函数之间的绑定。当信号函数被调用时,Qt会找到并执行与其绑定的槽函数。而且,Qt与允许一个信号和多个槽函数绑定,Qt会找到并执行一个信号绑定的所有槽函数。

3.一个简单的例子
在深入讨论信号与槽之前,我们先给一个尽可能简单的例子,使读者对这个机制有一个直观的印象。
classCExampleA:publicQObject{
Q_OBJECTpublic:
CExampleA({
m_Value=0;}
voidSetValue(intnNewVal{
if(m_Value==nNewVal{
return;}m_Value=nNewVal;emitstateChanged(m_Value;}
signals:voidstateChanged(intnNewVal;private:intm_Value;};



classCExampleB:publicQObject{
Q_OBJECTpublic:
CExampleB({;
}
publicslots:voidFunction(intnNewVal
{
qDebug(<<"newValues="<al;}};

如上述所示,CExampleA在行②使用Qt的关键字signals定义了信号stateChanged(当类CExampleA的状态改变时,行①“调用”这个信号函数。此处的调用格式与一般情形下的不同,①行使用了Qt的关键字emit,我们将其称为“发送”一个信号。
对于Qt的关键字signalsemitslots等,Qt的预处理器moc(meta-object-compiler将此处的关键字emit转换为符合C++语法标准的语句。我们使用信号与槽机制的时候,要确保包含信号与槽的类必须是类QObject的派生类。而且,在定义该类时应该在首部嵌入宏Q_OBJECT尤其需要注意的是,应该把这个类的声明放置在单独的头文件中(而不是潜入在源文件中),否则链接阶段会报错。槽函数的定义则非常简单,如行③所示,只是在该函数的存取控制关键字后面加上Qt关键字slots即可。

intmain(intargc,char*argv[]{
CExampleAa;CExampleBb;
QObject::connect(&a,SIGNAL(stateChanged(int,&b,SLOT(Function(int;a.SetValue(100;}

在工程main函数中,我们调用QObject的静态成员函数connect(绑定上述信号与槽,如行④所示。当修改类CExampleA的对象a的值时,行①发射信号stateChanged(,与其绑定的对象b的槽函数Function(会被执行,在控制台上出对象a新的值。
4.信号/4.1.信号(signal
当某个信号对其客户或所有者发生的内部状态发生改变,信号被一个对象发射。只有定义过这个信号的类及其派生类能够发射这个信号。当一个信号被发射时,与其相关联的槽将被立刻执行,就象一个正常的函数调用一样。信号/槽机制完全独立于任何GUI事件循环。只有当所有的槽返回以后发射函数(emit)才返回。



信号的声明是在头文件中进行的,Qtsignals关键字指出进入了信号声明区,随后即可声明自己的信号。例如,下面定义了三个信号:

signals:
voidmySignal(;
voidmySignal(int;
voidmySignal(QString&;

在上面的定义中,signalsQt的关键字,而非C++的。voidmySignal(定义了信号mySignal,这个信号没有携带参数;voidmySignal(int重载了mySignal,但是它携带一个整形参数。
信号函数应该满足以下语法约束:
1函数返回值是void类型,因为触发信号函数的目的是执行与其绑定的槽函数,无需信号函数返回任何值。
2开发人员只能声明、不能实现信号函数,Qtmoc工具会实现它。
3信号函数被moc自动设置为protected,因而只有包含一个信号函数那个类及其派生类才能使用该信号函数。4信号函数的参数个数、类型由开发人员自由设定,这些参数的职责是封装类的状态信息,并将这些信息传递给槽函数。
5只有QObject及其派生类才可以声明信号函数。
4.2.(slot
槽函数和普通的C++成员函数一样,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。
槽的声明也是在头文件中进行的。例如,下面声明了2个槽:

publicslots:
voidmySlot(;
voidmySlot(QString&;

槽函数的返回值是void类型,因为信号与槽机制是单向的:信号被发射后,与其绑定的槽函数会被执行,但不要求槽函数返回任何执行结果。和信号函数一样,只有QObject及其派生类才可以定义槽函数。
既然槽函数是普通的成员函数,因此与其它的函数一样,它们也有存取权限(publicprotectedprivate也就是说,它们能够控制其他类是否能够以正常的方式调用一个槽函数。但是,这些关键字对QObject::connect(函数不起作用。我们可以将protected甚至private槽函数和一个信号函数绑定。当该信号被发射后,甚至private的槽函数也会被执行。从某种意义上讲,Qt的信号与槽机制破坏了C++的存取控制规则,但是这种机制带来的灵活性远胜于可能导致的问题。
5.设计信号与槽5.1.关联使用
通过调用QObject::connect(函数可以绑定一个信号函数和一个槽函数,它的函数定义如



下:

boolQObject::connect(
constQObject*sender,SIGNAL(signal_function(,constQObject*receiver,SLOT(slot_function(,Qt::ConnectionTypetype=Qt::AutoConnection[static]

其中senderreceiver都是指向QObject对象的指针,前者指向发射信号的对象,后者指向处理信号的对象,两者分别被成为“发送者”及“接收者”signal_function以及slot_function分别是这两个对象中定义的信号函数和槽函数。当指定信号signal时必须使用Qt的宏SIGNAL(,当指定槽函数时必须使用宏SLOT(5.1.1.SIGNAL(SLOT(
SIGNAL(SLOT(宏的作用signalmethod是把转换成字符串,它们的定义如下:
#defineSLOT(a"1"#a#defineSIGNAL(a"2"#a

#’的作用是将a字符串化。例如:
connect(m_pPushBtn,SIGNAL(clicked(,this,SLOT(on_PushBtn_clicked(;

上述代码展开宏后,等价于:

connect(m_pPushBtn,"2clicked(",this,"1on_PushBtn_clicked(";
这是旧版的信号与槽的语法,它容易引发几个问题:
即使信号和槽不存在,编译不会出问题。只有运行时会给出警告并返回false,可是大部分用户并不检查返回值。
参数必须匹配,比如信号参数是int,槽参数是double,编译不会出现问题,但是运行的时候将会输出QObject::connect:Incompatiblesender/receiverarguments的错误。参数类型必须字面上一样,比如说都是int,但是其中一个typedef了一下:
typedefintmyInt;
connect(objectA,SIGNAL(sig(int,objectB,SLOT(slt(myInt;

出现的问题和第二点一样,编译不会出错。但是运行的时候将会输出QObject::connect:Incompatiblesender/receiverarguments”的错误。
信号函数和槽函数的参数列表只需包含参数类型,无须包含参数名。一般情况下,两个函数的原型应该完全相同。少数情况下,信号函数的参数可以多于槽函数的参数,当槽函数被执行时,多余的参数将被忽略。

5.1.2.Qt::ConnectionType
QObject::connect(函数的最后一个参数为枚举类型Qt::ConnectionTypetype,该枚举类型中定义的枚举常量以及它们的含义如表1-1所示。表中的枚举常量都被包含在命令空间



Qt中。对于DirectConnection以及BlockingQueuedConnection类型,只要当与一个信号函数绑定的所有槽函数执行完毕之后,emit语句后面的语句才会被执行。各槽函数的执行顺序与它们被绑定时的顺序相同。对于QueuedConnection类型,emit后面的语句会被立即执行,不会等待槽函数执行完毕。

枚举常量
AutoConnection
0
描述
(默认值如果发送者和接收者在同一个线程,信号发出后,槽函数立即执行,等同与Qt::DirectConnection如果信号和槽不在同一个线程,信号将排队,等待事件循环的处理,效果等同于Qt::QueuedConnection
DirectConnection
1
信号发送后立即传送给相关的槽,只有槽函数执行完毕返回后,发送信号"emit<信号>"之后的代码才被执
QueuedConnection
2

信号发送后排队,直到事件循环(eventloop有能力将它传递给槽;而不管槽函数有没有执行,发送信号"emit<信号>"之后的代码都会立即得到执行
BlockingQueuedConnection4
只有当发送者与接收者处于不同的线程时方可使用这种连接类型,否则将导致程序死锁。该连接类型与QueuedConnection类似,只不过在槽函数执行完毕之
UniqueConnection
0x80
前,发送者所属线程会被阻塞AutoConnection类似,只不过不会建立重复的连接,也就是说,如果要将被绑定的两个对象的信号函数、槽函数已经被绑定,就不再为它们建立连接
AutoCompatConnection
3
支持Qt3模式下的默认值。和AutoConnection类似,但是在某些情况下会输出警告信息
1-1信号与槽的连接类型

一个信号函数可以和多个槽函数绑定。例如在图1-2中,对象Esignal5Bslot2以及Cslot3绑定,signal5被发射时,两个槽函数都会被依次执行。多个信号函数可以和一个槽函数绑定。例如,Asignal1以及Dsignal3都和Bslot1绑定,其中任何一个信号被发射,槽函数就会被执行。Qt的信号与槽机制甚至支持信号函数和信号函数之间的绑定。例如,Dsignal4Esignal6绑定,后者再和Cslot4绑定。当signal4被发射时,signal6也会随即被发射,导致slot4被执行。




1-2信号函数与槽函数的各个对应关系

5.2.自动关联
"on__"QMetaObject::connectSlotsByName(函数即可实现信号自动连接到槽。例如:

Widget::Widget(QWidget*parent:QWidget(parent,ui(newUi::Widget{
m_pBtn=newQPushButton(this;m_pLabel=newQLabel(this;
m_pLabel->setGeometry(10,20,120,20;m_pBtn->setGeometry(10,50,40,20;//按钮设置对象名
m_pBtn->setObjectName("Btn";//设置信号与槽自动连接


QMetaObject::connectSlotsByName(this;}
voidWidget::on_Btn_clicked({
m_pLabel->setText("PushButtonwasclicked!";}

5.3.取消绑定
我们可以调用QObject的另外一个静态成员函数QObject::disconnect(断开信号与槽之间的连接,该函数的函数原型和QObject::connect(类似:





boolQObject::disconnect(
constQObject*sender,SIGNAL(signal_function(,constQObject*receiver,SLOT(slot_function([static]

有几种情况需要使用QObject::disconnect(函数:断开与某个对象相关联的任何对象
如果在某个对象中定义了一个或者多个信号,这些信号与另外若干个对象中的槽相关联,如果需要切断这些关联的话,就可以使用这种方法。例如:

disconnect(Object,0,0,0;Object->disconnect(;
断开与某个特定信号的任何关联,例如:
disconnect(Object,SIGNAL(Signal(,0,0;Object->disconnect(SIGNAL(Signal(;
断开两个对象之间的关联,例如:
disconnect(Object,0,Receiver,0;Object->disconnect(Receiver;

QObject::disconnect(函数中0可以用作一个通配符,分别表示任何信号、任何接收对象、接收对象中的任何槽函数。但是发射者sender不能为0,因为信号在发送时不能被断开。如何SIGNAL(signal_function(0,它断开任何信号与其绑定的槽函数。如果接收者0它将断开与其绑定的任何信号。SIGNAL(slot_function(0时,它将断开与接收者的任何信号连接。如果接收者设置为0,则SIGNAL(slot_function(必须等于0
当一个QObject对象被析构时,与之相关的所有连接都会被断开。因此,通常情况下开发人员没有必要显示地调用QObject::disconnect(;
本质上,信号与槽是QObject对象之间相互通信的一个机制。即使发送者和接收者相互不知道对方的任何信息,也可以完成通信任务。加之对象之间的连接是在运行时建立的,以各对象之间的耦合度被大幅降低。各个类的代码可以被独立地开发、测试,也更容易被复用。而且,在绑定一个信号函数和一个槽函数时,编译器会强制检查二者的函数原型是否匹配,以保证这种机制是安全的(对于新版本而言)
性能方面,信号与槽机制的确比回调机制更慢,但是由于它的执行时间非常短,通常情况下不会影响应用程序的性能。依据Qt文档提供的数据,当槽函数是非虚函数时,信号与槽机制比回调函数大约慢10倍。听起来这个速度似乎很慢,但是在i586-500机器上,如果一个信号函数只与一个槽函数绑定,1秒可以触发2,000,000次这样的信号;如果与两个槽函数绑定,1秒可以触发1,200,000次。因而用户实际上根本感觉不到该机制对程序性能的影响。

6.查找对话框中信号与槽的实现分析




本小节将实现图1-3中的对话框,并使用信号与槽机制,完成以下功能:只有当“Findwhat”编辑窗口中内容不为空时,Find”按钮才能被使能,否则,该按钮被禁用。

1-3指定搜索内容和搜索条件的对话框

Findwhat”编辑框具有类型QLineEditFind”按钮具有类QPushButton,对话框本身具有类型CFindDlg,它们所定义的信号与槽如图1-4所示。


1-4FindDlg中的信号与槽
QLineEdit以及QPushButtonQt库的类,我们无须定义。我们所需要做的是从Qt库的类QDialog派生出子类CFindDlg。该类的定义如下代码所示。如前文所述,由于该类在行②之后定义了2个槽函数,行①应该调用宏Q_OBJECT。行③之后的几行代码定义对话框中的控件。

classCFindDlg:publicQDialog{
Q_OBJECTpublic:
CFindDlg(QWidget*parent=0;
~CFindDlg(;
privateslots:voidEnableFindBtn(QStringChangedText;voidFindBtnClicked(;voidCloseBtnClicked(;



private:QLabel*m_pLabel;
QLineEdit*m_pLineEdit;QPushButton*m_pFindBtn;QPushButton*m_pCloseBtn;QCheckBox*m_pMatchCheckBox;QCheckBox*m_pBkSearchCheckBox;};



Qt信号与槽机制

相关推荐