信号槽是Qt
框架引以为豪的机制之一。熟练使用和理解信号槽,能够设计出解耦的非常漂亮的程序,有利于增强我们的技术设计能力。
所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。我们来看一下信号槽的使用:
#include <QApplication>
#include <QPushButton>
int
main
(
int
argc
,
char
*
argv
[
]
)
{
QApplication
app
(
argc
,
argv
)
;
QPushButton
button
(
"quit"
)
;
QObject
::
connect
(
&
button
,
SIGNAL
(
clicked
(
)
)
,
&
app
,
SLOT
(
quit
(
)
)
)
;
button
.
show
(
)
;
return
app
.
exec
(
)
;
}
我们使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意,即使quit()是QApplication的 static 函数,也必须传入一个对象指针。这也是信号槽语法的局限之处。另外,注意到connect()函数的 signal 和 slot 都是接受字符串,因此,不能将全局函数或者 Lambda 表达式(以后再说)传入connect()。一旦出现连接不成功的情况,Qt 4 是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
接下来看看三个不同的connect()重载:
bool
connect
(
const
QObject
*
,
const
char
*
,
const
QObject
*
,
const
char
*
,
Qt
::
ConnectionType
)
;
bool
connect
(
const
QObject
*
,
const
QMetaMethod
&
,
const
QObject
*
,
const
QMetaMethod
&
,
Qt
::
ConnectionType
)
;
bool
connect
(
const
QObject
*
,
const
char
*
,
const
char
*
,
Qt
::
ConnectionType
)
const
它们的返回值都是一样的,而且 signal 和 slot 只有const char *
这么一种形式。
信号槽不是 GUI 模块提供的,而是 Qt 核心特性之一。因此,我们可以在普通的控制台程序使用信号槽。
那么自定义信号槽就必不可少了,
自定义信号槽需要注意的事项:
1.
发送者和接收者都需要是QObject
的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
2.使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
3.槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
4.使用 emit 在恰当的位置发送信号;
5.使用QObject::connect()
函数连接信号和槽。
下面给出相应例子的代码:
#include <QObject>
// newspaper.h
class
Newspaper
:
public
QObject
{
Q_OBJECT
public
:
Newspaper
(
const
QString
&
name
)
:
m_name
(
name
)
{
}
void
send
(
)
const
{
emit
newPaper
(
m_name
)
;
}
signals
:
void
newPaper
(
const
QString
&
name
)
const
;
private
:
QString
m_name
;
}
;
// reader.h
#include <QObject>
#include <QDebug>
class
Reader
:
public
QObject
{
Q_OBJECT
public
:
Reader
(
)
{
}
public
slots
:
void
receiveNewspaper
(
const
QString
&
name
)
const
{
qDebug
(
)
<<
"Receives Newspaper: "
<<
name
;
}
}
;
// main.cpp
#include <QCoreApplication>
#include "newspaper.h"
#include "reader.h"
int
main
(
int
argc
,
char
*
argv
[
]
)
{
QCoreApplication
app
(
argc
,
argv
)
;
Newspaper
newspaper
(
"Newspaper A"
)
;
Reader
reader
;
QObject
::
connect
(
&
newspaper
,
SIGNAL
(
newPaper
(
QString
)
)
,
&
reader
,
SLOT
(
receiveNewspaper
(
QString
)
)
)
;
newspaper
.
send
(
)
;
return
app
.
exec
(
)
;
}
main()函数中,QObject::connect()函数,第二、第四个参数需要使用SIGNAL和SLOT这两个宏转换成字符串(具体事宜我们在上一节介绍过)。注意SIGNAL和SLOT的宏参数并不是取函数指针,而是除去返回值的函数声明,并且 const 这种参数修饰符是忽略不计的。
我们提到了“槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响”,public、private 这些修饰符是供编译器在编译期检查的,因此其影响在于编译期。
其连接是在运行时完成的,因此即便是 private 的槽函数也是可以作为槽进行连接的。