解讀QT信號與槽機制裏 QMetaObject::connectSlotsByName(QObject *o)的源碼

原文: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來連接信號和槽,那麼就有了幾個問題:

  1. 能不能對多個QObject設置同樣的objectName呢?
  2. 如果能,那麼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 on_TestButton_clicked(bool bVal);

    
void MainWindow::on_TestButton_clicked(bool bVal)
    {
        QObject 
*obj=sender();
        qDebug(
"TestButton is clicked by %s!%d\n",obj->objectName().toStdString().c_str(),bVal);
    }
複製代碼

 

然後編譯運行,結果出來了:

  1. 9個按鈕的objectName()都返回"TestButton"
  2. 只有第一個按鈕的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的實現,可以注意到以下幾個地方:

  1. 第7行,取得o的所有子對象,在測試的代碼裏,QPushButton都設置了this爲父對象,所以它們顯然會在這個列表裏出現
  2. 第8行,是一個遍歷o的方法的循環,o的信號和槽就在其中
  3. 第11行,對於方法名稱不是"on_"開頭的方法跳過不處理,這也說明,如果你在一個QObject子類裏定義了"on_"開頭的槽的話,一定會被connectSlotsByName函數進行搜索匹配的操作的
  4. 第14行開始到33行,開始遍歷o的所有的子對象,試圖匹配到與槽名稱以及信號名稱相應的子對象。首先取出其objectName()與槽名稱裏的第一個‘_’和第二個‘_’做名稱匹配。其次取出子對象的所有信號,與第二個‘_’之後部分做匹配。
  5. 如果匹配成功,則會執行36行的連接代碼。連接成功的話,就會在38行break中斷循環。

看到第5點,已經很明瞭了,對於同名的控件,connectSlotsByName只會連接子對象鏈表裏的第一個對象的信號到槽上。

總結和其他

 

做個小小的總結:

  1. 儘量不要讓QObject出現相同objectName的情況
  2. 如果同名connectSlotsByName只能給其中一個建立缺省的信號和槽的連接
  3. 如果出現大量編碼創建大量控件的情況,最好是自己去建立信號和槽的連接,而不是依賴connectSlotsByName來做到這個工作。connectSlotsByName更適合的任務是與desinger配合完成缺省的信號和槽的連接。

其他:

在測試過程中,曾經把ui->setupUi(this);放到了控件創建之前運行,結果運行時提示:

QMetaObject::connectSlotsByName: No matching signal for on_TestButton_clicked

從connectSlotsByName的代碼可以看到這實際上執行的是第46行,如果在調試程序中遇到這樣的信息,可以檢查一下,是否是控件的objectName與你編寫的槽裏的objectName並不相符。

http://code.google.com/p/klsudoku
Email: [email protected]
QQ羣:106249 ( Sudoku 羣)QQ羣:94388010( C++ 羣)

發佈了12 篇原創文章 · 獲贊 21 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章