信号与槽作ؓQT的核心机制在QT~程中有着q泛的应用,本文介绍了信号与槽的一些基本概c元对象工具以及在实际用过E中应注意的一些问题?/blockquote>
QT是一个跨q_的C++ GUI应用构架Q它提供了丰富的H口部g集,h面向对象、易于扩展、真正的lg~程{特点,更ؓ引h注目的是目前Linux上最为流行的KDE桌面环境是建立在QT库的基础之上。QT支持下列q_QMS/WINDOWS-95?8、NT?000QUNIX/X11-Linux、Sun Solaris、HP-UX、Digital Unix、IBM AIX、SGI IRIXQEMBEDDED-支持framebuffer的Linuxq_。伴随着KDE的快速发展和普及QQT很可能成为LinuxH口q_上进行Y件开发时的GUI首选?/p>
概述
信号和槽机制是QT的核心机Ӟ要精通QT~程必d信号和槽有所了解。信号和槽是一U高U接口,应用于对象之间的通信Q它是QT的核心特性,也是QT区别于其它工具包的重要地斏V信号和槽是QT自行定义的一U通信机制Q它独立于标准的C/C++语言Q因此要正确的处理信号和槽,必须借助一个称为mocQMeta Object CompilerQ的QT工具Q该工具是一个C++预处理程序,它ؓ高层ơ的事g处理自动生成所需要的附加代码?/p>
在我们所熟知的很多GUI工具包中Q窗口小部g(widget)都有一个回调函数用于响应它们能触发的每个动作,q个回调函数通常是一个指向某个函数的指针。但是,在QT中信号和槽取代了q些凌ؕ的函数指针,使得我们~写q些通信E序更ؓz明了?信号和槽能携带Q意数量和Lcd的参敎ͼ他们是类型完全安全的Q不会像回调函数那样产生core dumps?/p>
所有从QObject或其子类(例如Qwidget)z的类都能够包含信号和槽。当对象改变其状态时Q信号就p对象发射(emit)出去Q这是对象所要做的全部事情,它不知道另一端是谁在接收q个信号。这是真正的信息封装,它确保对象被当作一个真正的软glg来用。槽用于接收信号Q但它们是普通的对象成员函数。一个槽q不知道是否有Q何信号与自己相连接。而且Q对象ƈ不了解具体的通信机制?/p>
你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多的槽q行q接Q甚至于一个信号与另外一个信Lq接也是可能的,q时无论W一个信号什么时候发系l都立d第二个信号。MQ信号与槽构造了一个强大的部g~程机制?/p>
信号
当某个信号对其客h所有者发生的内部状态发生改变,信号被一个对象发。只?定义q这个信Lcd其派生类能够发射q个信号。当一个信可发射Ӟ与其相关联的槽将被立L行,p一个正常的函数调用一栗信?槽机制完全独立于MGUI事g循环。只有当所有的槽返回以后发函敎ͼemitQ才q回?如果存在多个槽与某个信号相关联,那么Q当q个信号被发时Q这些槽会一个接一个地 执行Q但是它们执行的序会是随机的、不定的,我们不能Zؓ地指定哪个先执行、哪 个后执行?/p>
信号的声明是在头文g中进行的QQT的signals关键字指入了信号声明区,随后卛_ 声明自己的信受例如,下面定义了三个信P
signals:
void mySignal();
void mySignal(int x);
void mySignalParam(int x,int y);
|
在上面的定义中,signals是QT的关键字Q而非C/C++的。接下来的一行void mySignal() 定义了信号mySignalQ这个信h有携带参敎ͼ接下来的一行void mySignal(int x)定义 了重名信号mySignalQ但是它携带一个整形参敎ͼq有点类gC++中的虚函数。从形式?讲信L声明与普通的C++函数是一LQ但是信号却没有函数体定义,另外Q信Lq回 cd都是voidQ不要指望能从信可回什么有用信息?/p>
信号由moc自动产生Q它们不应该?cpp文g中实现?/p>
?/span>
槽是普通的C++成员函数Q可以被正常调用Q它们唯一的特D性就是很多信号可以与其相兌。当与其兌的信可发射Ӟq个槽就会被调用。槽可以有参敎ͼ但槽的参C能有~省倹{?/p>
既然槽是普通的成员函数Q因此与其它的函CP它们也有存取权限。槽的存取权限决定了谁能够与其相兌。同普通的C++成员函数一P槽函C分ؓ三种cdQ即public slots、private slots和protected slots?/p>
- public slotsQ在q个区内声明的槽意味着M对象都可信号与之相q接。这对于lg~程非常有用Q你可以创徏彼此互不了解的对象,它们的信号与槽q行q接以便信息能够正确的传递?
- protected slotsQ在q个区内声明的槽意味着当前cd其子cd以将信号与之相连接。这适用于那些槽Q它们是cd现的一部分Q但是其界面接口却面向外部?
- private slotsQ在q个区内声明的槽意味着只有c自己可以将信号与之相连接。这适用于联p非常紧密的cR?
槽也能够声明函数Q这也是非常有用的?/p>
槽的声明也是在头文g中进行的。例如,下面声明了三个槽Q?/p>
public slots:
void mySlot();
void mySlot(int x);
void mySignalParam(int x,int y);
|
信号与槽的关?/span>
通过调用QObject对象的connect函数来将某个对象的信号与另外一个对象的槽函数相兌Q这样当发射者发信hQ接收者的槽函数将被调用。该函数的定义如下:
bool QObject::connect ( const QObject * sender, const char * signal,
const QObject * receiver, const char * member ) [static]
|
q个函数的作用就是将发射者sender对象中的信号signal与接收者receiver中的member槽函数联pv来。当指定信号signal时必M用QT的宏SIGNAL()Q当指定槽函数时必须使用宏SLOT()。如果发者与接收者属于同一个对象的话,那么在connect调用中接收者参数可以省略?/p>
例如Q下面定义了两个对象Q标{֯象label和滚动条对象scrollQƈvalueChanged()信号与标{֯象的setNum()相关联,另外信号q携带了一个整形参敎ͼq样标签L昄滚动条所处位|的倹{?/p>
QLabel *label = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect( scroll, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)) );
|
一个信L臌够与另一个信L兌Q看下面的例子:
class MyWidget : public QWidget
{
public:
MyWidget();
...
signals:
void aSignal();
...
private:
...
QPushButton *aButton;
};
MyWidget::MyWidget()
{
aButton = new QPushButton( this );
connect( aButton, SIGNAL(clicked()), SIGNAL(aSignal()) );
}
|
在上面的构造函CQMyWidget创徏了一个私有的按钮aButtonQ按钮的单击事g产生的信号clicked()与另外一个信号aSignal()q行了关联。这样一来,当信号clicked()被发时Q信号aSignal()也接着被发。当Ӟ你也可以直接单M件与某个U有的槽函数相关联,然后在槽中发aSignal()信号Q这L话似乎有点多余?/p>
当信号与槽没有必要l保持关联时Q我们可以用disconnect函数来断开q接。其定义如下Q?/p>
bool QObject::disconnect ( const QObject * sender, const char * signal,
const Object * receiver, const char * member ) [static]
|
q个函数断开发射者中的信号与接收者中的槽函数之间的关联?/p>
有三U情况必M用disconnect()函数Q?/p>
在disconnect函数?可以用作一个通配W,分别表示M信号、Q何接收对象、接收对象中的Q何槽函数。但是发者sender不能?Q其它三个参数的值可以等??/p>
元对象工?/span>
元对象编译器mocQmeta object compilerQ对C++文g中的cd明进行分析ƈ产生用于初始化元对象的C++代码Q元对象包含全部信号和槽的名字以及指向这些函数的指针?/p>
moc读C++源文Ӟ如果发现有Q_OBJECT宏声明的c,它就会生成另外一个C++源文Ӟq个新生成的文g中包含有该类的元对象代码。例如,假设我们有一个头文gmysignal.hQ在q个文g中包含有信号或槽的声明,那么在编译之?moc 工具׃Ҏ该文件自动生成一个名为mysignal.moc.h的C++源文件ƈ其提交l编译器Q类似地Q对应于mysignal.cpp文gmoc工具自动生成一个名为mysignal.moc.cpp文g提交l编译器?/p>
元对象代码是signal/slot机制所必须的。用moc产生的C++源文件必McdC赯行编译和q接Q或者用#include语句其包含到类的源文g中。mocq不扩展#include或?define宏定?它只是简单的跌所遇到的Q何预处理指o?/p>
E序样例
q里l出了一个简单的样例E序Q程序中定义了三个信受三个槽函数Q然后将信号与槽q行了关联,每个槽函数只是简单的弹出一个对话框H口。读者可以用kdevelop生成一个简单的QT应用E序Q然后将下面的代码添加到相应的程序中厅R?/p>
信号和槽函数的声明一般位于头文g中,同时在类声明的开始位|必d上Q_OBJECT语句Q这条语句是不可~少的,它将告诉~译器在~译之前必须先应用moc工具q行扩展。关键字signals指出随后开始信L声明Q这里signals用的是复数Ş式而非单数Qsiganls没有public、private、protected{属性,q点不同于slots。另外,signals、slots关键字是QT自己定义的,不是C++中的关键字?/p>
信号的声明类g函数的声明而非变量的声明,左边要有cdQ右边要有括P如果要向槽中传递参数的话,在括号中指定每个形式参数的类型,当然QŞ式参数的个数可以多于一个?/p>
关键字slots指出随后开始槽的声明,q里slots用的也是复数形式?/p>
槽的声明与普通函数的声明一P可以携带零或多个形式参数。既然信L声明cM于普通C++函数的声明,那么Q信号也可采用C++中虚函数的Ş式进行声明,卛_名但参数不同。例如,W一ơ定义的void mySignal()没有带参敎ͼ而第二次定义的却带有参数Q从q里我们可以看到QT的信h制是非常灉|的?/p>
信号与槽之间的联pdM先用connect函数q行指定。如果要断开二者之间的联系Q可以用函数disconnect?/p>
//tsignal.h
...
class TsignalApp:public QMainWindow
{
Q_OBJECT
...
//信号声明?
signals:
//声明信号mySignal()
void mySignal();
//声明信号mySignal(int)
void mySignal(int x);
//声明信号mySignalParam(int,int)
void mySignalParam(int x,int y);
//槽声明区
public slots:
//声明槽函数mySlot()
void mySlot();
//声明槽函数mySlot(int)
void mySlot(int x);
//声明槽函数mySignalParam (intQint)
void mySignalParam(int x,int y);
}
...
//tsignal.cpp
...
TsignalApp::TsignalApp()
{
...
//信号mySignal()与槽mySlot()相关?
connect(this,SIGNAL(mySignal()),SLOT(mySlot()));
//信号mySignal(int)与槽mySlot(int)相关?
connect(this,SIGNAL(mySignal(int)),SLOT(mySlot(int)));
//信号mySignalParam(int,int)与槽mySlotParam(int,int)相关?
connect(this,SIGNAL(mySignalParam(int,int)),SLOT(mySlotParam(int,int)));
}
// 定义槽函数mySlot()
void TsignalApp::mySlot()
{
QMessageBox::about(this,"Tsignal", "This is a signal/slot sample without
parameter.");
}
// 定义槽函数mySlot(int)
void TsignalApp::mySlot(int x)
{
QMessageBox::about(this,"Tsignal", "This is a signal/slot sample with one
parameter.");
}
// 定义槽函数mySlotParam(int,int)
void TsignalApp::mySlotParam(int x,int y)
{
char s[256];
sprintf(s,"x:%d y:%d",x,y);
QMessageBox::about(this,"Tsignal", s);
}
void TsignalApp::slotFileNew()
{
//发射信号mySignal()
emit mySignal();
//发射信号mySignal(int)
emit mySignal(5);
//发射信号mySignalParam(5Q?00)
emit mySignalParam(5,100);
}
|
|
|
应注意的问题
信号与槽机制是比较灵zȝQ但有些局限性我们必M解,q样在实际的使用q程中做到有的放矢,避免产生一些错误。下面就介绍一下这斚w的情c?/p>
1Q信号与槽的效率是非帔R的,但是同真正的回调函数比较hQ由于增加了灉|性,因此在速度上还是有所损失Q当然这U损qҎ说是比较的Q通过在一台i586-133的机器上试?0微秒Q运行LinuxQ,可见q种机制所提供的简z性、灵zL还是值得的。但如果我们要追求高效率的话Q比如在实时pȝ中就要尽可能的少用这U机制?/p>
2Q信号与槽机制与普通函数的调用一P如果使用不当的话Q在E序执行时也有可能生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环Q即在槽中再ơ发所接收到的同样信号。例?在前面给出的例子中如果在mySlot()槽函C加上语句emit mySignal()卛_形成d@环?/p>
3Q如果一个信号与多个槽相联系的话Q那么,当这个信可发射Ӟ与之相关的槽被激zȝ序是随机的?/p>
4. 宏定义不能用在signal和slot的参C?/p>
既然moc工具不扩?defineQ因此,在signals和slots中携带参数的宏就不能正确地工作,如果不带参数是可以的。例如,下面的例子中带有参数的宏SIGNEDNESS(a)作ؓ信号的参数是不合语法的:
#ifdef ultrix
#define SIGNEDNESS(a) unsigned a
#else
#define SIGNEDNESS(a) a
#endif
class Whatever : public QObject
{
[...]
signals:
void someSignal( SIGNEDNESS(a) );
[...]
};
|
5. 构造函C能用在signals或者slots声明区域内?/p>
的确Q将一个构造函数放在signals或者slots区内有点不可理解Q无论如何,不能它们放在private slots、protected slots或者public slots区内。下面的用法是不合语法要求的Q?/p>
class SomeClass : public QObject
{
Q_OBJECT
public slots:
SomeClass( QObject *parent, const char *name )
: QObject( parent, name ) {} // 在槽声明区内声明构造函C合语?
[...]
};
|
6. 函数指针不能作ؓ信号或槽的参数?/p>
例如Q下面的例子中将void (*applyFunction)(QList*, void*)作ؓ参数是不合语法的Q?/p>
class someClass : public QObject
{
Q_OBJECT
[...]
public slots:
void apply(void (*applyFunction)(QList*, void*), char*); // 不合语法
};
|
你可以采用下面的Ҏl过q个限制Q?/p>
typedef void (*ApplyFunctionType)(QList*, void*);
class someClass : public QObject
{
Q_OBJECT
[...]
public slots:
void apply( ApplyFunctionType, char *);
};
|
7.信号与槽不能有缺省参数?/p>
既然signal->slotl定是发生在q行时刻Q那么,从概念上讲用缺省参数是困难的。下面的用法是不合理的:
class SomeClass : public QObject
{
Q_OBJECT
public slots:
void someSlot(int x=100); // x的缺省值定义成100Q在槽函数声明中使用是错误的
};
|
8.信号与槽也不能携带模板类参数?/p>
如果信受槽声明为模板类参数的话Q即使moc工具不报告错误,也不可能得到预期的结果?例如Q下面的例子中当信号发射Ӟ槽函C会被正确调用Q?/p>
[...]
public slots:
void MyWidget::setLocation (pair<int,int> location);
[...]
public signals:
void MyObject::moved (pair<int,int> location);
|
但是Q你可以使用typedef语句来绕q这个限制。如下所C:
typedef pair<int,int> IntPair;
[...]
public slots:
void MyWidget::setLocation (IntPair location);
[...]
public signals:
void MyObject::moved (IntPair location);
|
q样使用的话Q你可以得到正的l果?/p>
9.嵌套的类不能位于信号或槽区域内,也不能有信号或者槽?/p>
例如Q下面的例子中,在class B中声明槽b()是不合语法的Q在信号区内声明槽b()也是不合语法的?/p>
class A
{
Q_OBJECT
public:
class B
{
public slots: // 在嵌套类中声明槽不合语法
void b();
[....]
};
signals:
class B
{
// 在信号区内声明嵌套类不合语法
void b();
[....]
}:
};
|
10.友元声明不能位于信号或者槽声明区内?/p>
相反Q它们应该在普通C++的private、protected或者public区内q行声明。下面的例子是不合语法规范的Q?/p>
class someClass : public QObject
{
Q_OBJECT
[...]
signals: //信号定义?
friend class ClassTemplate; // 此处定义不合语法
};
|
参考资?
关于作?/span>