译者注:Qt是一套跨平台的C++ GUI应用程序框架,最近非常流行。它的信号与插槽(Signals and Slots)机制与Windows的消息机制有很大差别,特此翻译其手册中这一篇,为学习亦为好奇,若错误百出还请指正为谢。
Signals and Slots
Signals and slots are used for communication between objects. The signal/slot mechanism is a central feature of Qt and probably the part that differs most from other toolkits.
信号与插槽用于对象间通讯。信号/插槽机制是Qt的重要特征,这也许就是它与其它工具包差别最大之处。
In GUI programming we often want a change in one widget to be notified to another widget. More generally, we want objects of any kind to be able to communicate with one another. For example if we were parsing an XML file we might want to notify a list view that we're using to represent the XML file's structure whenever we encounter a new tag.
在图形用户界面编程中,我们常常希望一个窗口接收到其他窗口的通知之时做出改变。更普遍地,我们希望任意类型的对象能够与其它对象通讯。例如,在我们解析XML文件时,我们也许希望在遇到一个新的标记时,能够给一个我们用来显示XML文件的结构的列表发出通知。
Older toolkits achieve this kind of communication using callbacks. A callback is a pointer to a function, so if you want a processing function to notify you about some event you pass a pointer to another function (the callback) to the processing function. The processing function then calls the callback when appropriate. Callbacks have two fundamental flaws. Firstly they are not type safe. We can never be certain that the processing function will call the callback with the correct arguments. Secondly the callback is strongly coupled to the processing function since the processing function must know which callback to call.
老的工具包使用回调来达到这样的目的。回调是一个指向函数的指针,所以如果你希望一个处理函数通知你某些事件发生了,你可以传递一个指向其他函数的指针(回调)给处理函数。处理函数会在适当的时候调用回调函数。回调有两个基本缺点。第一,它不是类型安全的。我们不能保证处理函数会以正确的参数来回调。第二,回调与处理函数强耦合,因为处理函数必须知道哪个回调被调用。
Old-fashioned callbacks impair component programming
In Qt we have an alternative to the callback technique. We use signals and slots. A signal is emitted when a particular event occurs. Qt's widgets have many pre-defined signals, but we can always subclass to add our own. A slot is a function that is called in reponse to a particular signal. Qt's widgets have many pre-defined slots, but it is common practice to add your own slots so that you can handle the signals that you are interested in.
在Qt中,我们有了除回调技术的另外的选择。我们使用信号与插槽。当特殊事件发生的时候,信号就被发出了。Qt的窗口有许多预定义信号,但我们也可以添加我们自己的。一个插槽就是一个函数,被称作特定信号的响应。
Signals and Slots facilitate true type-safe component programming
The signals and slots mechanism is type safe: the signature of a signal must match the signature of the receiving slot. (In fact a slot may have a shorter signature than the signal it receives because it can ignore extra arguments.) Since the signatures are compatible, the compiler can help us detect type mismatches. Signals and slots are loosely coupled: a class which emits a signal neither knows nor cares which slots receive the signal. Qt's signals and slots mechanism ensures that if you connect a signal to a slot, the slot will be called with the signal's parameters at the right time. Signals and slots can take any number of arguments of any type. They are completely typesafe: no more callback core dumps!
信号与插槽机制是类型安全的:信号的签名必须和接收插槽的签名匹配。(事实上插槽的签名或许比它收到的信号的签名短,因为它可以忽略掉额外的参数。)因为签名是兼容的,编译器可以帮助我们检测出类型不匹配。信号与插槽是松耦合的:一个发出信号的类不知道也不关心哪一个插槽接收到这个信号。Qt的信号和插槽机制保证了如果你连接一个信号到一个插槽上,插槽会在正确的时间,被以信号的参数调用。信号与插槽可以携带任意个、任意类型的参数。他们完全类型安全,没有回调的内核传储(core dumps)!
All classes that inherit from QObject or one of its subclasses (e.g. QWidget) can contain signals and slots. Signals are emitted by objects when they change their state in a way that may be interesting to the outside world. This is all the object does to communicate. It does not know or care whether anything is receiving the signals it emits. This is true information encapsulation, and ensures that the object can be used as a software component.
所有从Qobject或者它的一个子类(比如:QWidget)继承的类都包含信号与插槽。在对象以某种方式改变了它可能让外界感兴趣的状态时,信号就会被对象激发。这就是所有对象通讯所做的。它并不知道也不感兴趣接收什么。这是真正的信息封装,保证了对象能被当作软件组件来使用。
Slots can be used for receiving signals, but they are normal member functions. A slot does not know if it has any signals connected to it. Again, the object does not know about the communication mechanism and can be used as a true software component.
插槽能被用来接收信号,但它们是普通成员函数。插槽不知道是否有信号连接到它。对象也不知道通信机制,可以作为真正的软件组件来使用。
You can connect as many signals as you want to a single slot, and a signal can be connected to as many slots as you desire. It is even possible to connect a signal directly to another signal. (This will emit the second signal immediately whenever the first is emitted.)
你可以连接任意信号到你希望的单一插槽,并且一个信号也能够被连接到你所期望的多个插槽。甚至可能直接连接一个信号到其他信号。(这意味当前一个信号被激发时,会立即激发另一个信号)。
Together, signals and slots make up a powerful component programming mechanism.
总之,信号与插槽建立了强大的组件编程机制。
A Small Example
A minimal C++ class declaration might read:
一个最小的C++类可以声明如下:
class Foo
{
public:
Foo();
int value() const { return val; }
void setValue( int );
private:
int val;
};
A small Qt class might read:
一个小的Qt类可以像这样:
class Foo : public QObject
{
Q_OBJECT
public:
Foo();
int value() const { return val; }
public slots:
void setValue( int );
signals:
void valueChanged( int );
private:
int val;
};
This class has the same internal state, and public methods to access the state, but in addition it has support for component programming using signals and slots: this class can tell the outside world that its state has changed by emitting a signal, valueChanged(), and it has a slot which other objects may send signals to.
这个类拥有相同的内部状态和访问状态的公有方法,但另外它拥有利用信号与插槽进行组件编程的支持:这个类能够通过激发一个信号valueChanged()来告诉外界它的状态已改变,并且它也有一个可以供其他对象发送信号的插槽。
All classes that contain signals and/or slots must mention Q_OBJECT in their declaration.
所有包含信号和/或插槽的类必须在它们的声明里添加Q_OBJECT。
Slots are implemented by the application programmer. Here is a possible implementation of Foo::setValue():
插槽由应用程序员实现,这是Foo::setValue()的一种可能的实现:
void Foo::setValue( int v )
{
if ( v != val ) {
val = v;
emit valueChanged(v);
}
}
The line emit valueChanged(v) emits the signal valueChanged from the object. As you can see, you emit a signal by using emit signal(arguments).
emit valueChanged(v)
这行从对象里激发了信号valueChanged。正如你所能看到的,你使用emit 信号(参数)激发一个信号。
Here is one way to connect two of these objects together:
这里有一种将两个这种对象连接到一起的方法。
Foo a, b;
connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
b.setValue( 11 ); // a == undefined b == 11
a.setValue( 79 ); // a == 79 b == 79
b.value();
Calling a.setValue(79) will make a emit a valueChanged() signal, which b will receive in its setValue() slot, i.e. b.setValue(79) is called. b will then, in turn, emit the same valueChanged() signal, but since no slot has been connected to b's valueChanged() signal, nothing happens (the signal disappears).
调用a.setValue(79)回激发一个valueChanged()信号,b会在它的setValue插槽中收到这个信号,也就是b.setValue(79)被调用。然后,b会轮流激发同样的valueChanged()信号,但因为没有插槽被连接到b的valueChanged()信号,于是没有信号发生(信号消失)。
Note that the setValue() function sets the value and emits the signal only if v != val. This prevents infinite looping in the case of cyclic connections (e.g. if b.valueChanged() were connected to a.setValue()).
注意setValue()函数为value赋值并激发信号仅当v!=val。这避免了循环连接的情况下(例如:如果b.valueChanged()也被连接到了a.setValue())的无限循环。
This example illustrates that objects can work together without knowing each other, as long as there is someone around to set up a connection between them initially.
这个例子讲解了对象可以在不知道对方的情况下协同工作,只要在他们初始化时为他们建立连接。
The preprocessor changes or removes the signals, slots and emit keywords so that the compiler is presented with standard C++.
预处理器改变或者去掉signals,slots和emit关键字,所以编译器以标准C++来对待它。
Run the moc on class definitions that contain signals or slots. This produces a C++ source file which should be compiled and linked with the other object files for the application.
运行moc(Meta Object Compiler)来处理包含信号或者插槽的类定义。这产生一个能够被编译并且与应用程序中其他目标文件连接起来的C++源代码。
Signals
信号
Signals are emitted by an object when its internal state has changed in some way that might be interesting to the object's client or owner. Only the class that defines a signal and its subclasses can emit the signal.
当对象的客户或者所有者所感兴趣的对象内部状态改变了,信号就被激发。只有定义了信号的类及其子类能够激发信号。
A list box, for example, emits both highlighted() and activated() signals. Most objects will probably only be interested in activated() but some may want to know about which item in the list box is currently highlighted. If the signal is interesting to two different objects you just connect the signal to slots in both objects.
一个列表框,既能激发heighlighed()信号也能激发activated()信号。大多数对象可能只对activated()感兴趣,但有时希望知道列表框中的哪一个元素当前是高亮显示的。如果信号被两个不同的对象所感兴趣,你可以连接信号到两个对象的插槽。
When a signal is emitted, the slots connected to it are executed immediately, just like a normal function call. The signal/slot mechanism is totally independent of any GUI event loop. The emit will return when all slots have returned.
当信号被激发了,连接到它的插槽会立即执行,就像普通的函数调用那样。信号/插槽机制完全独立于任何GUI事件循环。当所有插槽返回了,emit才会返回。
If several slots are connected to one signal, the slots will be executed one after the other, in an arbitrary order, when the signal is emitted.
如果几个插槽被连接到同一信号,这些插槽会在信号被激发的时候以任意秩序依次执行。
Signals are automatically generated by the moc and must not be implemented in the .cpp file. They can never have return types (i.e. use void).
信号会自动被moc自动生成,不必在.cpp里面实现。它们永远不会返回值(也就是void)。
A note about arguments. Our experience shows that signals and slots are more reusable if they do not use special types. If QScrollBar::valueChanged() were to use a special type such as the hypothetical QRangeControl::Range, it could only be connected to slots designed specifically for QRangeControl. Something as simple as the program in Tutorial 5 would be impossible.
关于参数。我们的经验显示如果信号与插槽没有使用专门的类型,他们的可重用性会更高。如果QscrollBar::valueChanged()使用了一个指定类型,假设是QRangeControl::Range,它就只能被连接到明确到QRangeControl。事情和Tutorial5里面一样简单是不可能的。
Slots
插槽
A slot is called when a signal connected to it is emitted. Slots are normal C++ functions and can be called normally; their only special feature is that signals can be connected to them. A slot's arguments cannot have default values, and, like signals, it is rarely wise to use your own custom types for slot arguments.
插槽会在链街道它的信号被激发时调用。插槽是普通的C++函数能被普通的调用。
Since slots are normal member functions with just a little extra spice, they have access rights like ordinary member functions. A slot's access right determines who can connect to it:
因为插槽是略为扩展的普通成员函数,他们拥有普通成员函数的访问权限。插槽的访问权限决定了谁能连接它:
A public slots: section contains slots that anyone can connect signals to. This is very useful for component programming: you create objects that know nothing about each other, connect their signals and slots so that information is passed correctly, and, like a model railway, turn it on and leave it running.
一个public slots:段包含了谁都能够连接的插槽。这对组件编程非常有用:你创建对对方一无所知的对象,连接它们的信号与插槽以便信息能够正确传递,就像铁路模型一样,打开它就可以让它自己运行。
A protected slots: section contains slots that this class and its subclasses may connect signals to. This is intended for slots that are part of the class' implementation rather than its interface to the rest of the world.
一个protected slots:段包含了它自身及其子类可以连接的插槽。这是有意让插槽成为类实现的一部分,而不是对外界的接口。
A private slots: section contains slots that only the class itself may connect signals to. This is intended for very tightly connected classes, where even subclasses aren't trusted to get the connections right.
一个private slots:段包含了只能被类自身连接的插槽。这有意对紧连接类,甚至子类也不能希望得到正确的连接。
You can also define slots to be virtual, which we have found quite useful in practice.
你也可以定义插槽为virtual的,我们发现这在实际中特别有用。
The signals and slots mechanism is efficient, but not quite as fast as "real" callbacks. Signals and slots are slightly slower because of the increased flexibility they provide, although the difference for real applications is insignificant. In general, emitting a signal that is connected to some slots, is approximately ten times slower than calling the receivers directly, with non-virtual function calls. This is the overhead required to locate the connection object, to safely iterate over all connections (i.e. checking that subsequent receivers have not been destroyed during the emission) and to marshall any parameters in a generic fashion. While ten non-virtual function calls may sound like a lot, it's much less overhead than any 'new' or 'delete' operation, for example. As soon as you perform a string, vector or list operation that behind the scene requires 'new' or 'delete', the signals and slots overhead is only responsible for a very small proportion of the complete function call costs. The same is true whenever you do a system call in a slot - or indirectly call more than ten functions. On an i586-500, you can emit around 2,000,000 signals per second connected to one receiver, or around 1,200,000 per second connected to two receivers. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won't even notice.
信号与插槽机制是有效率的,但不能完全和“真实”的回调相比。信号与插槽会因为它带来了灵活性的增加而稍慢一些,尽管这些差别在真正的应用程序中无关紧要。通常,激发一个已连接到某些插槽的信号会比直接调用接收者慢10倍,这里假设没有虚函数调用。这是定位连接对象所需要的,为了安全的迭代(遍历)所有连接(也就是检查下一个接收者在激发期间没有被破坏)。也许10次非虚函数调用听起来很多,但它远少于’new’或者’delete’操作。比如,当你进行一个字符串、向量或者列表操作,这需要隐藏的’new’或者’delete’,信号与插槽只占用了非常全部函数调用开销的非常少的一部分。这对你在插槽里进行系统调用或者间接调用10个以上的函数也是一样的。在一台i586-500上,你可以每秒激发大约2,000,000次已连接一个接收者上的信号,或者大约1,200,000次已连接到两个接收者上的信号。信号与插槽机制的简单性和灵活性的开销是值得的,甚至你的用户不会发觉。
Meta Object Information
元对象信息
The meta object compiler (moc) parses the class declaration in a C++ file and generates C++ code that initializes the meta object. The meta object contains names of all signal and slot members, as well as pointers to these functions. (For more information on Qt's Meta Object System, see Why doesn't Qt use templates for signals and slots?.)
元对象编译器(moc)解析C++文件中的类声明并在初始化生成C++代码。元对象包含包含所有信号和插槽成员的名字,就像这些函数指针一样。(更多关于Qt 元对象系统的信息,见Why doesn't Qt use templates for signals and slots?)
The meta object contains additional information such as the object's class name. You can also check if an object inherits a specific class, for example:
元对象包含像类名这样的附加信息,你也可以检查一个对象是否继承自一个指定的类,例如:
if ( widget->inherits("QButton") ) {
// yes, it is a push button, radio button etc.
}
A Real Example
一个例子
Here is a simple commented example (code fragments from qlcdnumber.h ).
这有一个简单的带注释的例子(qlcdnumber.h中的代码片断)。
#include "qframe.h"
#include "qbitarray.h"
class QLCDNumber : public QFrame
QLCDNumber inherits QObject, which has most of the signal/slot knowledge, via QFrame and QWidget, and #include's the relevant declarations.
QLCDNumber通过Qframe和Qwidget间接继承自拥有最多信号/插槽知识的Qobject,并且包含了相关声明。
{
Q_OBJECT
Q_OBJECT is expanded by the preprocessor to declare several member functions that are implemented by the moc; if you get compiler errors along the lines of "virtual function QButton::className not defined" you have probably forgotten to run the moc or to include the moc output in the link command.
Q_OBJECT被预处理器扩展声明来声明几个由moc实现的成员函数;如果你得到像这样的一行编译器(应该是链接器)错误”virtual function Qbutton::className not defined”,也许是你忘了运行moc或者忘了在链接命令包含moc输出。
public:
QLCDNumber( QWidget *parent=0, const char *name=0 );
QLCDNumber( uint numDigits, QWidget *parent=0, const char *name=0 );
It's not obviously relevant to the moc, but if you inherit QWidget you almost certainly want to have the parent and name arguments in your constructors, and pass them to the parent constructor.
这与moc没有明显的关系,但如果你继承了Qwidget并确信希望在你的构造函数里有parent和name参数,将它们传入父类的构造函数。
Some destructors and member functions are omitted here; the moc ignores member functions.
一些析构函数和成员函数在这里省略了;moc忽略了成员函数。
signals:
void overflow();
QLCDNumber emits a signal when it is asked to show an impossible value.
QLCDNumber在被要求显示不可能的值时会激发一个信号。
If you don't care about overflow, or you know that overflow cannot occur, you can ignore the overflow() signal, i.e. don't connect it to any slot.
如果你不关心溢出,或者你知道溢出不可能发生,你可以忽略overflow()信号,也就是不将它与任何插槽连接。
If, on the other hand, you want to call two different error functions when the number overflows, simply connect the signal to two different slots. Qt will call both (in arbitrary order).
另一方面,如果你希望当数字溢出时调用两个不同的错误处理函数,简单地将信号与两个不同的插槽连接。Qt两个都会调用(以任意次序)。
public slots:
void display( int num );
void display( double num );
void display( const char *str );
void setHexMode();
void setDecMode();
void setOctMode();
void setBinMode();
void smallDecimalPoint( bool );
A slot is a receiving function, used to get information about state changes in other widgets. QLCDNumber uses it, as the code above indicates, to set the displayed number. Since display() is part of the class' interface with the rest of the program, the slot is public.
插槽是一个接收函数用来取得其他部件(widgets)状态改变的信息。QLCDNumber使用了它,就像上面的代码展示的那样,用来设置显示数字。因为display()是类接口的一部分,所以slot是公有的。
Several of the example programs connect the newValue signal of a QScrollBar to the display slot, so the LCD number continuously shows the value of the scroll bar.
例程连接QscrollBar的newValue信号到display插槽,所以LCD持续显示滚动条的值。
Note that display() is overloaded; Qt will select the appropriate version when you connect a signal to the slot. With callbacks, you'd have to find five different names and keep track of the types yourself.
注意display()被重载了;当你连接一个信号到这个插槽时,Qt会选择适当的版本。而回调,你得找5个不同的名字并自己的来跟踪类型。
Some irrelevant member functions have been omitted from this example.
一些不相关的成员函数从例子中被省略了。
};
Copyright © 2001 Trolltech Trademarks Qt version 3.0.1
---------------------------------------------------------------------------------------------------------------------------------------------------
QT的 signal & slot机制
signals和slots机制是QT的根本。
slots和c++的成员函数(member function)几乎一样的,它们能定义为virtual,能overloaded,能定义为public,protected或private。能和c++其他成员函数一样
被直接调用,参量(paramters)能定义为任何类型,他与其它成员函数不同的是它能连接signal,当signal发射(emitted)时,它能自动调用。
它们格式一般为:
connect(sender,SIGNAL(signal),receiver,SLOT(slot));
sender和receiver必需为指向一个QObject对象的指针。而signal和slot只需指出参量类型而不用写出产量名称。
以下是一些可能发生的例子。
a:一个signal能连接多个slots。
例如: connect(slider,SIGNAL(valueChange(int)),spinBox,SLOT(setValue(int)));
connect(slider,SIGNAL(valueChange(int)),this,SLOT(updateValue(int)));
b:多个signal能连接一个slots。
connect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()),
this, SLOT(handleMathError()));
c:一个signal能连接其他signal。
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SIGNAL(updateRecord(const QString &)));
d:连接能够删除。
disconnect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
要成功连接signal到slot,必须要参量类型及数量相同。
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(processReply(int, const QString &)));
但万事无绝对,如果signal比slot参量多,多出参量可以忽略。
例如:connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(checkErrorCode(int)));