}
};
如上述所示,类CExampleA在行②使用Qt的关键字signals定义了信号stateChanged(),当类CExampleA的状态改变时,行①“调用”这个信号函数。
此处的调用格式与一般情形下的不同,①行使用了Qt的关键字emit,我们将其称为“发送”一个信号。
对于Qt的关键字signals、emit及slots等,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)才返回。
信号的声明是在头文件中进行的,Qt的signals关键字指出进入了信号声明区,随后即可声明自己的信号。
例如,下面定义了三个信号:
signals:
voidmySignal();
voidmySignal(int);
voidmySignal(QString&);
在上面的定义中,signals是Qt的关键字,而非C++的。
voidmySignal()定义了信号mySignal,这个信号没有携带参数;voidmySignal(int)重载了mySignal,但是它携带一个整形参数。
信号函数应该满足以下语法约束:
1)函数返回值是void类型,因为触发信号函数的目的是执行与其绑定的槽函数,无需信号函数返回任何值。
2)开发人员只能声明、不能实现信号函数,Qt的moc工具会实现它。
3)信号函数被moc自动设置为protected,因而只有包含一个信号函数那个类及其派生类才能使用该信号函数。
4)信号函数的参数个数、类型由开发人员自由设定,这些参数的职责是封装类的状态信息,并将这些信息传递给槽函数。
5)只有QObject及其派生类才可以声明信号函数。
4.2.槽(slot)
槽函数和普通的C++成员函数一样,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。
当与其关联的信号被发射时,这个槽就会被调用。
槽可以有参数,但槽的参数不能有缺省值。
槽的声明也是在头文件中进行的。
例如,下面声明了2个槽:
publicslots:
voidmySlot();
voidmySlot(QString&);
槽函数的返回值是void类型,因为信号与槽机制是单向的:
信号被发射后,与其绑定的槽函数会被执行,但不要求槽函数返回任何执行结果。
和信号函数一样,只有QObject及其派生类才可以定义槽函数。
既然槽函数是普通的成员函数,因此与其它的函数一样,它们也有存取权限(public、protected、private)。
也就是说,它们能够控制其他类是否能够以正常的方式调用一个槽函数。
但是,这些关键字对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]
其中sender及receiver都是指向QObject对象的指针,前者指向发射信号的对象,后者指向处理信号的对象,两者分别被成为“发送者”及“接收者”。
signal_function以及slot_function分别是这两个对象中定义的信号函数和槽函数。
当指定信号signal时必须使用Qt的宏SIGNAL(),当指定槽函数时必须使用宏SLOT()。
5.1.1.SIGNAL()和SLOT()
SIGNAL()和SLOT()宏的作用signal和method是把转换成字符串,它们的定义如下:
#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<信号>"之后的代码都会立即得到执行
BlockingQueuedConnection
4
只有当发送者与接收者处于不同的线程时方可使用这种连接类型,否则将导致程序死锁。
该连接类型与QueuedConnection类似,只不过在槽函数执行完毕之前,发送者所属线程会被阻塞
UniqueConnection
0x80
与AutoConnection类似,只不过不会建立重复的连接,也就是说,如果要将被绑定的两个对象的信号函数、槽函数已经被绑定,就不再为它们建立连接
AutoCompatConnection
3
支持Qt3模式下的默认值。
和AutoConnection类似,但是在某些情况下会输出警告信息
表1-1信号与槽的连接类型
一个信号函数可以和多个槽函数绑定。
例如在图1-2中,对象E的signal5和B的slot2以及C的slot3绑定,当signal5被发射时,两个槽函数都会被依次执行。
多个信号函数可以和一个槽函数绑定。
例如,A的signal1以及D的signal3都和B的slot1绑定,其中任何一个信号被发射,槽函数就会被执行。
Qt的信号与槽机制甚至支持信号函数和信号函数之间的绑定。
例如,D的signal4和E的signal6绑定,后者再和C的slot4绑定。
当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”编辑框具有类型QLineEdit,“Find”按钮具有类QPushButton,对话框本身具有类型CFindDlg,它们所定义的信号与槽如图1-4所示。
图1-4FindDlg中的信号与槽
类QLineEdit以及QPushButton是Qt库的类,我们无须定义。
我们所需要做的是从Qt库的类QDialog派生出子类CFindDlg。
该类的定义如下代码所示。
如前文所述,由于该类在行②之后定义了2个槽函数,行①应该调用宏Q_OBJECT。
行③之后的几行代码定义对话框中的控件。
classCFindDlg:
publicQDialog
{
Q_OBJECT①
public:
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;
};