综合了一下网上资源,整理得出。
QT 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象、易于扩展、真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 QT 库的基础之上。QT 支持下列平台:MS/WINDOWS-95、98、NT 和 2000;UNIX/X11-Linux、Sun Solaris、HP-UX、Digital Unix、IBM AIX、SGI IRIX;EMBEDDED- 支持 framebuffer 的 Linux 平台。伴随着 KDE 的快速发展和普及,QT 很可能成为 Linux 窗口平台上进行软件开发时的 GUI 首选。
connect,是QT中的连接函数,将信号发送者sender对象中的信号signal与接受者receiver中的member槽函数联系起来。当指定信号signal时必须使用宏SIGNAL(),当指定槽函数时必须使用宏SLOT(),如果发送者与连接者属于同一个对象时,那么在connect调用中接受者参数可以忽略。
信号和槽机制是 QT 的核心机制,要精通 QT 编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方。信号和槽是 QT 自行定义的一种通信机制,它独立于标准的 C/C++ 语言,因此要正确的处理信号和槽,必须借助一个称为 moc(Meta Object Compiler)的 QT 工具,该工具是一个 C++ 预处理程序,它为高层次的事件处理自动生成所需要的附加代码。
在我们所熟知的很多 GUI 工具包中,窗口小部件 (widget) 都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在 QT 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。 信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的,不会像回调函数那样产生 core dumps。
所有从 QObject 或其子类 ( 例如 Qwidget) 派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射 (emit) 出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。槽用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。
你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多的槽进行连接,甚至于将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将立刻发射第二个信号。总之,信号与槽构造了一个强大的部件编程机制。
通过调用 QObject 对象的 connect 函数来将某个对象的信号与另外一个对象的槽函数相关联,这样当发射者发射信号时,接收者的槽函数将被调用。该函数的定义如下:
bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * member ) [static]
这个函数的作用就是将发射者 sender 对象中的信号 signal 与接收者 receiver 中的 member 槽函数联系起来。当指定信号 signal 时必须使用 QT 的宏 SIGNAL(),当指定槽函数时必须使用宏 SLOT()。如果发射者与接收者属于同一个对象的话,那么在 connect 调用中接收者参数可以省略。
例如,下面定义了两个对象:标签对象 label 和滚动条对象 scroll,并将 valueChanged() 信号与标签对象的 setNum() 相关联,另外信号还携带了一个整形参数,这样标签总是显示滚动条所处位置的值。
QLabel *label = new QLabel; QScrollBar *scroll = new QScrollBar; QObject::connect( scroll, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)) );
一个信号甚至能够与另一个信号相关联,看下面的例子:
class MyWidget : public QWidget { public: MyWidget(); ... signals: void aSignal(); ... private: ... QPushButton *aButton; }; MyWidget::MyWidget() { aButton = new QPushButton( this ); connect( aButton, SIGNAL(clicked()), SIGNAL(aSignal()) ); }
在上面的构造函数中,MyWidget 创建了一个私有的按钮 aButton,按钮的单击事件产生的信号 clicked() 与另外一个信号 aSignal() 进行了关联。这样一来,当信号 clicked() 被发射时,信号 aSignal() 也接着被发射。当然,你也可以直接将单击事件与某个私有的槽函数相关联,然后在槽中发射 aSignal() 信号,这样的话似乎有点多余。
当信号与槽没有必要继续保持关联时,我们可以使用 disconnect 函数来断开连接。其定义如下:
bool QObject::disconnect ( const QObject * sender, const char * signal, const Object * receiver, const char * member ) [static]
这个函数断开发射者中的信号与接收者中的槽函数之间的关联。
有三种情况必须使用 disconnect() 函数:
断开与某个对象相关联的任何对象。这似乎有点不可理解,事实上,当我们在某个对象中定义了一个或者多个信号,这些信号与另外若干个对象中的槽相关联,如果我们要切断这些关联的话,就可以利用这个方法,非常之简洁。
disconnect( myObject, 0, 0, 0 ) 或者 myObject->disconnect()
断开与某个特定信号的任何关联。
disconnect( myObject, SIGNAL(mySignal()), 0, 0 ) 或者 myObject->disconnect( SIGNAL(mySignal()) )
断开两个对象之间的关联。
disconnect( myObject, 0, myReceiver, 0 ) 或者 myObject->disconnect( myReceiver )
在 disconnect 函数中 0 可以用作一个通配符,分别表示任何信号、任何接收对象、接收对象中的任何槽函数。但是发射者 sender 不能为 0,其它三个参数的值可以等于 0。
从Qobject(QObject.h)源码中可以看到QObject::connect的定义是这样的:
[cpp] view plaincopy
1. static bool connect(const QObject *sender, const char *signal,
2. const QObject *receiver, const char *member, Qt::ConnectionType =
3. #ifdef qdoc
4. Qt::AutoConnection
5. #else
6. #ifdef QT3_SUPPORT
7. Qt::AutoCompatConnection
8. #else
9. Qt::AutoConnection
10. #endif
11. #endif
12. );
13. inline bool connect(const QObject *sender, const char *signal,
14. const char *member, Qt::ConnectionType type =
15. #ifdef qdoc
16. Qt::AutoConnection
17. #else
18. #ifdef QT3_SUPPORT
19. Qt::AutoCompatConnection
20. #else
21. Qt::AutoConnection
22. #endif
23. #endif
24. ) const;
其中第二个connect的实现其实只有一句话:
[cpp] view plaincopy
1. { return connect(asender, asignal, this, amember, atype); }
所以对于connect函数的学习其实就是研究第一个connect函数。
我们在使用connect函数的时候一般是这样调用的:
[cpp] view plaincopy
1. connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
这里用到了两个宏:SIGNAL() 和SLOT();通过connect声明可以知道这两个宏最后倒是得到一个const char*类型。
在qobjectdefs.h中可以看到SIGNAL() 和SLOT()的宏定义:
[cpp] view plaincopy
1. #ifndef QT_NO_DEBUG
2. # define QLOCATION "\0"__FILE__":"QTOSTRING(__LINE__)
3. # define METHOD(a) qFlagLocation("0"#a QLOCATION)
4. # define SLOT(a) qFlagLocation("1"#a QLOCATION)
5. # define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
6. #else
7. # define METHOD(a) "0"#a
8. # define SLOT(a) "1"#a
9. # define SIGNAL(a) "2"#a
10. #endif
所以这两个宏的作用就是把函数名转换为字符串并且在前面加上标识符。
比如:SIGNAL(read())展开后就是"2read()";同理SLOT(read())展开后就是"1read()"。
[cpp] view plaincopy
1. connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
2. 实际上就是connect(sender,“2signal()”,receiver,“1slot())”;
搞明白了实际的参数就可以来看connect的真正实现过程了,在QObject.cpp文件中可以找到connect的实现代码。
[cpp] view plaincopy
1. bool QObject::connect(const QObject *sender, const char *signal,
2. const QObject *receiver, const char *method,
3. Qt::ConnectionType type)
4. {
5. {
6. const void *cbdata[] = { sender, signal, receiver, method, &type };
7. if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
8. return true;
9. }
10.
11. if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
12. qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
13. sender ? sender->metaObject()->className() : "(null)",
14. (signal && *signal) ? signal+1 : "(null)",
15. receiver ? receiver->metaObject()->className() : "(null)",
16. (method && *method) ? method+1 : "(null)");
17. return false;
18. }
19. QByteArray tmp_signal_name;
20.
21. if (!check_signal_macro(sender, signal, "connect", "bind"))
22. return false;
23. const QMetaObject *smeta = sender->metaObject();
24. const char *signal_arg = signal;
25. ++signal; //skip code
26. int signal_index = smeta->indexOfSignal(signal);
27. if (signal_index < 0) {
28. // check for normalized signatures
29. tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
30. signal = tmp_signal_name.constData() + 1;
31.
32. signal_index = smeta->indexOfSignal(signal);
33. if (signal_index < 0) {
34. err_method_notfound(sender, signal_arg, "connect");
35. err_info_about_objects("connect", sender, receiver);
36. return false;
37. }
38. }
39.
40. QByteArray tmp_method_name;
41. int membcode = extract_code(method);
42.
43. if (!check_method_code(membcode, receiver, method, "connect"))
44. return false;
45. const char *method_arg = method;
46. ++method; // skip code
47.
48. const QMetaObject *rmeta = receiver->metaObject();
49. int method_index = -1;
50. switch (membcode) {
51. case QSLOT_CODE:
52. method_index = rmeta->indexOfSlot(method);
53. break;
54. case QSIGNAL_CODE:
55. method_index = rmeta->indexOfSignal(method);
56. break;
57. }
58. if (method_index < 0) {
59. // check for normalized methods
60. tmp_method_name = QMetaObject::normalizedSignature(method);
61. method = tmp_method_name.constData();
62. switch (membcode) {
63. case QSLOT_CODE:
64. method_index = rmeta->indexOfSlot(method);
65. break;
66. case QSIGNAL_CODE:
67. method_index = rmeta->indexOfSignal(method);
68. break;
69. }
70. }
71.
72. if (method_index < 0) {
73. err_method_notfound(receiver, method_arg, "connect");
74. err_info_about_objects("connect", sender, receiver);
75. return false;
76. }
77. if (!QMetaObject::checkConnectArgs(signal, method)) {
78. qWarning("QObject::connect: Incompatible sender/receiver arguments"
79. "\n %s::%s --> %s::%s",
80. sender->metaObject()->className(), signal,
81. receiver->metaObject()->className(), method);
82. return false;
83. }
84.
85. int *types = 0;
86. if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
87. && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
88. return false;
89.
90. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
91. const_cast
92. return true;
93. }
上面是去除了debug代码的connect实现。
[cpp] view plaincopy
1. const void *cbdata[] = { sender, signal, receiver, method, &type };
2. if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
3. return true;
判断连接是否已经建立。
QInternal::ConnectCallback在qglobal.cpp中实现。
[cpp] view plaincopy
1. bool QInternal::activateCallbacks(Callback cb, void **parameters)
2. {
3. Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()", "Callback id must be a valid id");
4.
5. QInternal_CallBackTable *cbt = global_callback_table();
6. if (cbt && cb < cbt->callbacks.size()) {
7. QList
8. bool ret = false;
9. for (int i=0; i 10. ret |= (callbacks.at(i))(parameters); 11. return ret; 12. } 13. return false; 14. }
QInternal_CallBackTable 定义为(qglobal.cpp)
[cpp] view plaincopy
1. struct QInternal_CallBackTable {
2. QVector
3. };
qInternalCallback定义为(qnamespace.h)
[cpp] view plaincopy
1. typedef bool (*qInternalCallback)(void **);这是一个函数指针 返回值是bool,只有一个参数为void**。这个指针在调用registerCallback加入列表。
[cpp] view plaincopy
1. if (!check_signal_macro(sender, signal, "connect", "bind"))
2. return false;
判断signal是否合法。
在QObject.cpp文件中可以找到check_signal_macro的实现
[cpp] view plaincopy
1. static bool check_signal_macro(const QObject *sender, const char *signal,
2. const char *func, const char *op)
3. {
4. int sigcode = extract_code(signal);
5. if (sigcode != QSIGNAL_CODE) {
6. if (sigcode == QSLOT_CODE)
7. qWarning("Object::%s: Attempt to %s non-signal %s::%s",
8. func, op, sender->metaObject()->className(), signal+1);
9. else
10. qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
11. func, op, sender->metaObject()->className(), signal);
12. return false;
13. }
14. return true;
15. }
extract的实现也在QObject中,它就是去字符串第一个字符,并且只取低2位的值。
[cpp] view plaincopy
1. static int extract_code(const char *member)
2. {
3. // extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE
4. return (((int)(*member) - '0') & 0x3);
5. }
这里又有两个宏:QSIGNAL_CODE 和QSLOT_CODE。它们也是在qobjectdefs.h文件中定义的。
[cpp] view plaincopy
1. #ifdef QT3_SUPPORT
2. #define METHOD_CODE 0 // member type codes
3. #define SLOT_CODE 1
4. #define SIGNAL_CODE 2
5. #endif
这个定义与之前的SIGNAL和SLOT的定义是对应的。
[cpp] view plaincopy
1. const QMetaObject *smeta = sender->metaObject();
2. const char *signal_arg = signal;
3. ++signal; //skip code
4. int signal_index = smeta->indexOfSignal(signal);
5. if (signal_index < 0) {
6. // check for normalized signatures
7. tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
8. signal = tmp_signal_name.constData() + 1;
9.
10. signal_index = smeta->indexOfSignal(signal);
11. if (signal_index < 0) {
12. err_method_notfound(sender, signal_arg, "connect");
13. err_info_about_objects("connect", sender, receiver);
14. return false;
15. }
16. }
获取signal的索引。
metaObject()是在moc_name.cpp文件中生成的。
[cpp] view plaincopy
1. return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
其中staticMetaObject也是在moc文件中定义的
[cpp] view plaincopy
1. const QMetaObject MainWindow::staticMetaObject = {
2. { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow,
3. qt_meta_data_MainWindow, 0 }
4. };
qt_meta_stringdata_MainWindow(具体名字和类名有关)就是staticconstchar[]类型。它记录了全部的signals和slots等的函数名、返回值和参数表的信息。
qt_meta_data_MainWindow(具体名字和类名有关)是staticconstuint[]类型。它记录了每一个函数的函数名、返回值和参数表在qt_meta_stringdata_MainWindow中的索引。同时它还记录了每一个函数的类型具体在qmetaobject.cpp文件中定义。
[cpp] view plaincopy
1. enum MethodFlags {
2. AccessPrivate = 0x00,
3. AccessProtected = 0x01,
4. AccessPublic = 0x02,
5. AccessMask = 0x03, //mask
6.
7. MethodMethod = 0x00,
8. MethodSignal = 0x04,
9. MethodSlot = 0x08,
10. MethodConstructor = 0x0c,
11. MethodTypeMask = 0x0c,
12.
13. MethodCompatibility = 0x10,
14. MethodCloned = 0x20,
15. MethodScriptable = 0x40
16. };
indexOfSignal(signal);的实现在qmetaobject.cpp中。其主要作用是利用qt_meta_stringdata_MainWindow 和qt_meta_data_MainWindow查找已经定义了的signal并返回索引。
[cpp] view plaincopy
1. QByteArray tmp_method_name;
2. int membcode = extract_code(method);
3.
4. if (!check_method_code(membcode, receiver, method, "connect"))
5. return false;
6. const char *method_arg = method;
7. ++method; // skip code
8.
9. const QMetaObject *rmeta = receiver->metaObject();
10. int method_index = -1;
11. switch (membcode) {
12. case QSLOT_CODE:
13. method_index = rmeta->indexOfSlot(method);
14. break;
15. case QSIGNAL_CODE:
16. method_index = rmeta->indexOfSignal(method);
17. break;
18. }
19. if (method_index < 0) {
20. // check for normalized methods
21. tmp_method_name = QMetaObject::normalizedSignature(method);
22. method = tmp_method_name.constData();
23. switch (membcode) {
24. case QSLOT_CODE:
25. method_index = rmeta->indexOfSlot(method);
26. break;
27. case QSIGNAL_CODE:
28. method_index = rmeta->indexOfSignal(method);
29. break;
30. }
31. }
32.
33. if (method_index < 0) {
34. err_method_notfound(receiver, method_arg, "connect");
35. err_info_about_objects("connect", sender, receiver);
36. return false;
37. }
校验method并且查找它的索引。过程与signal类似。
[cpp] view plaincopy
1. if (!QMetaObject::checkConnectArgs(signal, method)) {
2. qWarning("QObject::connect: Incompatible sender/receiver arguments"
3. "\n %s::%s --> %s::%s",
4. sender->metaObject()->className(), signal,
5. receiver->metaObject()->className(), method);
6. return false;
7. }
判断signal和method是否兼容,checkConnectArgs函数的在qmetaObject.cpp文件中实现。这个函数校验了signal和method的参数。当两者的参数一致或method参数比signal参数少(method与signal前几个参数一致)的时候返回true,其它返回false。
[cpp] view plaincopy
1. int *types = 0;
2. if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
3. && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
4. return false;
如果是以发消息的方式执行method就需要对参数类型进行判断。queuedConnectionTypes在QObject.cpp实现。实际上是在QMetatype.cpp中定义了一个
static conststruct { constchar * typeName;int type;} types[];在这里记录了全部类型和名称如({"void",QMetaType::Void});Void在Qmetatype.h中定义。
[cpp] view plaincopy
1. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
调用QMetaObject的connect函数,再次不详细写出。
[cpp] view plaincopy
1. const_cast
最后调用虚函数connectNotify表示connect已经执行完成。