QT的信号(signal)与槽(slots)机制
信号和槽机制是QT的核心机制,应用于对象之间的通信.信号和槽是QT自行定义的一种通信机制,,借助一个称为moc(Meta Object Compiler)的QT工具,该工具是一个C++预处理程序,它为高层次的事件处理自动生成所需要的附加代码。
窗口小部件(widget)都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在QT中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。
所有从QObject或其子类(例如Qwidget)派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射(emit)出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。槽用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。
你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多的槽进行连接,甚至于将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将立刻发射第二个信号。
当所有的槽返回以后发射函数(emit)才返回。 如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地 执行,但是它们执行的顺序将会是随机的、不确定的,我们不能人为地指定哪个先执行、哪 个后执行。
出处:http://blog.csdn.net/pppjob/archive/2008/01/29/2071567.aspx
XL Signal and Slots for C++
Introduction
Signals and slots are used for communication between objects. There are instances when we want some objects to know the changes of a particular object without having to write code for each interested object. This design is common in GUI programming, where another widget needs to be notified implicitly of the change of one widget. Since the widget needs to be usable everywhere, the design should be as generic as possible. The event-generating widget should not need to know how or which widgets are informed of the event.
In older C-based toolkits, this functionality is achieved by using "callbacks" - registering pointers to functions to a processing function. The problem with callbacks is that they are not type safe, prone to crashes, and are strongly tied to the processing function.
Signal and Slots is an implementation of the Observer design pattern made popular by the Qt toolkit. It provides a much safer alternative to callbacks in addition to being built on the principles of object-oriented programming. Qt implemented this feature by dynamically adding extra information to classes during compile time with the help of an external meta object compiler.
XL Signal and Slots achieves the same functionality without the meta compiler by taking advantage of C++ templates. This direct approach is very fast compared to Qt's string-based approach with the added benefit of:
- Allowing all member functions to be treated as slots.
- No longer needing specification of non-standard macros during class definitions.
At the same time, the library tries to follow the design of Qt as closely as possible where it:
- Automatically severs the connection if either receiver or sender is destroyed so your program does not crash during the process of emitting a signal.
- Lets receiver objects know which sender Object emitted the signal
- Lets the compiler catch type mismatches.
- Allows a slot to have a shorter signature than the signal without having to rebind types.
- Optionally allows objects to be organized as object trees and achieving a concept that is more or less similar to garbage-collection.
The library aims to achieve the core signal and slots functionality very well and at the same time be as less complicated as possible. In around a thousand lines of code, the library is capable of being embedded into the application in a very tiny amount of space.
A simple example
Signals are emitted when a particular event occurs. Once a Signal is emitted, all slots connected to it are immediately called. If you are interested in an event, you need to connect a signal to a slot. Slots can be any standard member function of a class derived from XL::Object. Its access rights determine which signals can connect to it. The argument of a slot must match that of the signal it intends to be connected to. Similar to Qt's Slots, all slots must return only void.
A class that has a signal declaration might be defined as:
class Counter { public: XL::Signal< int > valueChanged; private: void secondsElapse(int sec); };
A class that has a slot interested in the above signal might be defined as:
class Clock: public XL::Object { public: void displaySeconds(int s); };
If a Clock object wants to be automatically informed by a Counter component to display the time, the Clock's displaySeconds() method must be connected to the Counter's valueChanged signal:
Counter counter; Clock clock; counter.valueChanged.connect(&clock, &Clock::displaySeconds);
Signals have a connect method that takes a pointer of an Object and its corresponding member function in standard pointer to member function syntax. An optional SLOT macro is also provided to clarify your code. When using the SLOT macro, you no longer need to use the pointer to member function syntax when specifying a slot.
counter.valueChanged.connect(SLOT(&clock, Clock::displaySeconds));
Creating Signals
You can create your own signals. Any number of signals can be connected to a single slot and vice-versa. A signal can also be directly connected to another signal of the same type. The second signal will be immediately emitted whenever the first is emitted.
The correct syntax in creating a Signal is:
XL::Signal<TypeArg1, TypeArg2, ..., TypeArgN> signalobject;
The type of arguments is specified in the template parameter list of the Signal. Signals and slots can take up to seven arguments of any type. You can connect any slot that matches the type of the signal. The library ensures that the right slot will be called with the signal's parameters at the right time. You can even connect slots that have a shorter argument signature than the signal and it will ignore the extra arguments.
class Sender { public: XL::Signal<int, const MyType&, const std::string& > signalobject; }; class Receiver { public: void function3(int t, const MyType& mt, const std::string &s); void function2(int t, const MyType& mt); void function1(int t); void function0(); void otherfunc(const std::string &s); }; Sender s; Receiver r; s.signaloject.connect(SLOT(&r, Receiver::function3)); // exact match of slot. s.signaloject.connect(SLOT(&r, Receiver::function2)); // okay. ignore extra arguments s.signaloject.connect(SLOT(&r, Receiver::function1)); // okay. ignore extra arguments s.signaloject.connect(SLOT(&r, Receiver::function0)); // okay. ignore extra arguments s.signaloject.connect(SLOT(&r, Receiver::otherfunc)); // compiler error! type mismatch
Triggering a Signal
A signal will trigger all signal and slots connected to it immediately once it is emitted. There are two ways to emit a Signal. You can either use Object::emit() or Signal::emit(). The latter method is used on freestanding signals. Here's how we emit the valueChanges signal on our initial Clock and Counter example:
void Counter::secondsElapse(int sec) { valueChanged.emit(sec); }
Use Object::emit() to let connecting slots know which sender object initiated the signal. If you need to use this feature, make sure that the class holding the Signal is also derived from XL::Object. You can then use Object::sender() inside the slot to obtain a pointer to the object that sent the signal.
class Counter: public XL::Object { public: XL::Signal< int > valueChanged; private: void secondsElapse(int sec) { emit(valueChanged(sec)); } }; Counter counter; Clock clock; counter.valueChanged.connect(SLOT(&clock, Clock::displaySeconds)); Clock::displaySeconds(int s) { Counter* c = (Counter *) sender(); c->someMethod(); }
The Signal's emission may be deferred at a later time. To do this, you need to specify false to the autoexec argument of the Signal::emit() method. This is set to true by default. If autoexec is set to false, Signal::emit() returns a BlockedSignal object that pauses the triggering of the stored slots until it is explictly triggered later by BlockedSignal::play().
void Counter::secondsElapse(int sec) { BlockedSignal< int > b = valueChanged.emit(sec,false); // does not trigger the slots b.play(); // trigger the slots now. }
Object Trees and Object Ownership
You can optionally organize XL Objects in object trees. Though this feature is not required for Signal and Slots functionality, it comes as an added bonus when you derive your objects from XL::Object. One of the biggest advantage when using Object trees in your application is that you no longer need to manually manage the lifetime cycle of your objects. When an XL::Object is created with another object as parent, it's added to the parent's children list, and is deleted when the parent is. This approach is ideally suited when mixing GUI objects with dynamically allocated Objects representing extra information. Child objects may also be deleted manually, they will remove themselves from their parent in the process.
For example, you can define an XL::Object-derived class with dynamically allocated members that are themselves derived from XL::Object. You no longer need to worry about not deleting the objects at a later time since XL takes care of it all for us.
class MyObject : public XL::Object { public: MyObject(XL::Object* parent): XL::Object(parent) { pb = new PushButton(this,..); sq = new SqlDatabase(this,...); } ~MyObject() { // no need to delete sub objects } private: PushButton* pb; SqlDatabase* sq; };
Where to get XLObject
Latest version is available via SVN
svn co https://xlobject.svn.sourceforge.net/svnroot/xlobject xlobject
Download old releases via sourceforge download .
Cool software using XLObject as a foundation:
Qt is a registered trademark of Trolltech in Norway and worldwide.