原文:http://www.cnblogs.com/ttylikl/archive/2009/07/13/1522770.html
介紹
connectSlotsByName 是一個QMetaObject類裏的static函數,其定義如下:
static void connectSlotsByName(QObject *o);
其作用是如其名稱一樣,用來將QObject *o裏的子孫QObject的某些信號按照其objectName連接到o的槽上。
起因
爲啥會對這個函數產生一探究竟的想法呢?——
既然是根據objectName來連接信號和槽,那麼就有了幾個問題:
- 能不能對多個QObject設置同樣的objectName呢?
- 如果能,那麼connectSlotsByName會連接多少個QObject的信號到指定的槽上呢?
測試結果
有了疑問,第一個應該做的事情,當然是編寫代碼進行測試了。
在測試的主窗口類構造函數在“ui->setupUi(this); ”語句前編寫如下代碼:
for(int i=0;i<9;++i)
{
QPushButton *btn=new QPushButton(this);
btn->setObjectName(“TestButton”);
qDebug(btn->objectName().toStdString().c_str());
}
ui->setupUi(this);
QMetaObject::connectSlotsByName()這個函數會在ui->setupUi(this);裏被調用執行。
然後在主窗口裏增加下面的槽定義很代碼:
void MainWindow::on_TestButton_clicked(bool bVal)
{
QObject *obj=sender();
qDebug("TestButton is clicked by %s!%d\n",obj->objectName().toStdString().c_str(),bVal);
}
然後編譯運行,結果出來了:
- 9個按鈕的objectName()都返回"TestButton"
- 只有第一個按鈕的clicked信號被連接到了on_TestButton_clicked槽上
第一個結論與我的猜想相符(後來看了QObject的源碼,也是比較簡單的),第二個結論與我的猜想有點不同,我本來猜想,應該是9個按鈕的clicked信號應該都可以連接到這個on_TestButton_clicked槽上的,但是卻只有第一個連接上了,這是爲什麼呢?
讓我們看看connectSlotsByName都幹了些什麼吧。
connectSlotsByName的源碼解讀
1 void QMetaObject::connectSlotsByName(QObject *o)
2 {
3 if (!o)
4 return;
5 const QMetaObject *mo = o->metaObject();
6 Q_ASSERT(mo);
7 const QObjectList list = qFindChildren<QObject *>(o, QString());
8 for (int i = 0; i < mo->methodCount(); ++i) {
9 const char *slot = mo->method(i).signature();
10 Q_ASSERT(slot);
11 if (slot[0] != 'o' || slot[1] != 'n' || slot[2] != '_')
12 continue;
13 bool foundIt = false;
14 for(int j = 0; j < list.count(); ++j) {
15 const QObject *co = list.at(j);
16 QByteArray objName = co->objectName().toAscii();
17 int len = objName.length();
18 if (!len || qstrncmp(slot + 3, objName.data(), len) || slot[len+3] != '_')
19 continue;
20 const QMetaObject *smo = co->metaObject();
21 int sigIndex = smo->indexOfMethod(slot + len + 4);
22 if (sigIndex < 0) { // search for compatible signals
23 int slotlen = qstrlen(slot + len + 4) - 1;
24 for (int k = 0; k < co->metaObject()->methodCount(); ++k) {
25 if (smo->method(k).methodType() != QMetaMethod::Signal)
26 continue;
27
28 if (!qstrncmp(smo->method(k).signature(), slot + len + 4, slotlen)) {
29 sigIndex = k;
30 break;
31 }
32 }
33 }
34 if (sigIndex < 0)
35 continue;
36 if (QMetaObject::connect(co, sigIndex, o, i)) {
37 foundIt = true;
38 break;
39 }
40 }
41 if (foundIt) {
42 // we found our slot, now skip all overloads
43 while (mo->method(i + 1).attributes() & QMetaMethod::Cloned)
44 ++i;
45 } else if (!(mo->method(i).attributes() & QMetaMethod::Cloned)) {
46 qWarning("QMetaObject::connectSlotsByName: No matching signal for %s", slot);
47 }
48 }
49 }
看connectSlotsByName的實現,可以注意到以下幾個地方:
- 第7行,取得o的所有子對象,在測試的代碼裏,QPushButton都設置了this爲父對象,所以它們顯然會在這個列表裏出現
- 第8行,是一個遍歷o的方法的循環,o的信號和槽就在其中
- 第11行,對於方法名稱不是"on_"開頭的方法跳過不處理,這也說明,如果你在一個QObject子類裏定義了"on_"開頭的槽的話,一定會被connectSlotsByName函數進行搜索匹配的操作的
- 第14行開始到33行,開始遍歷o的所有的子對象,試圖匹配到與槽名稱以及信號名稱相應的子對象。首先取出其objectName()與槽名稱裏的第一個‘_’和第二個‘_’做名稱匹配。其次取出子對象的所有信號,與第二個‘_’之後部分做匹配。
- 如果匹配成功,則會執行36行的連接代碼。連接成功的話,就會在38行break中斷循環。
看到第5點,已經很明瞭了,對於同名的控件,connectSlotsByName只會連接子對象鏈表裏的第一個對象的信號到槽上。
總結和其他
做個小小的總結:
- 儘量不要讓QObject出現相同objectName的情況
- 如果同名connectSlotsByName只能給其中一個建立缺省的信號和槽的連接
- 如果出現大量編碼創建大量控件的情況,最好是自己去建立信號和槽的連接,而不是依賴connectSlotsByName來做到這個工作。connectSlotsByName更適合的任務是與desinger配合完成缺省的信號和槽的連接。
其他:
在測試過程中,曾經把ui->setupUi(this);放到了控件創建之前運行,結果運行時提示:
QMetaObject::connectSlotsByName: No matching signal for on_TestButton_clicked
從connectSlotsByName的代碼可以看到這實際上執行的是第46行,如果在調試程序中遇到這樣的信息,可以檢查一下,是否是控件的objectName與你編寫的槽裏的objectName並不相符。
Email: [email protected]
QQ羣:106249 ( Sudoku 羣)QQ羣:94388010( C++ 羣)