咱們在使用 Qt 建立一個窗口 MyWidget 時, Qt Creator 會幫咱們建立出 "MyWidget.h", "MyWidget.cpp", "MyWidget.ui" 這三個文件. 咱們使用 Qt Designer 打開 MyWidget.ui 文件, 拖一個 QPushButton 上去, Qt Designer 默認給這個按鈕設置一個對象名 "pushButton". 在該按鈕上右鍵選擇轉到槽, 選擇clicked()
信號, Qt Creator 就會在 MyWidget 類中生成一個槽函數 void on_pushButton_clicked()
. 咱們只須要在這個槽函數中添加本身的邏輯就好了.數組
有沒有感受到和平時本身寫信號槽的時候不同? 沒有 connect? 可是程序跑起來, 按下按鈕就自動執行該槽函數了呀. 從結果看, 確定是 connect 過了. 因此咱們不難想到, 必定是信號槽被自動 connect 了.函數
若是讓咱們本身實現的話, 將一個對象的槽函數與其UI文件中定義的某對象的信號鏈接起來, 須要如下幾步:ui
咱們知道 "MyWidget.ui" 文件會被處理, 並生成 "ui_MyWidget.h" 文件. 這個文件會被包含在 MyWidget 類的實現中. 那咱們就先來看看這個 "ui_MyWidget.h" 文件吧.spa
ui_MyWidget.h 中有一個 Ui_MyWidget 類. 咱們在類成員變量中發現了QPushButton *pushButton;
, 這個就是咱們以前拖上去的按鈕. 除此以外, 還有兩個成員函數void setupUi(QWidget *MyWidget)
和 void retranslateUi(QWidget *MyWidget)
.code
咱們知道 retranslateUi 函數是在當程序語言改變時, 用來刷新UI中顯示語言的. 是國際化相關的內容, 在這裏咱們先忽略. 咱們先看看 setupUi 函數中作了些什麼.對象
void setupUi(QWidget *MyWidget) {
if (MyWidget->objectName().isEmpty())
MyWidget->setObjectName(QStringLiteral("MyWidget"));
MyWidget->resize(400, 300);
pushButton = new QPushButton(MyWidget);
pushButton->setObjectName(QStringLiteral("pushButton"));
pushButton->setGeometry(QRect(220, 220, 75, 23));
retranslateUi(MyWidget);
複製代碼
這半部分用來將控件建立出來, 進行一些設置並使其做爲 MyWidget 的子對象. 咱們接着往下看.遞歸
QMetaObject::connectSlotsByName(MyWidget);
} // setupUi
複製代碼
在最後調用了 QMetaObject::connectSlotsByName()
函數, 從函數名咱們就知道其功能是 "經過名字鏈接槽函數". 這麼看來, 自動鏈接信號槽就是它作的了. QMetaObject 在 "qobjectdefs.h" 中定義, 在 "qobject.cpp" 中實現.ip
qobjectdefs.h文檔
struct Q_CORE_EXPORT QMetaObject {
...
// internal slot-name based connect
static void connectSlotsByName(QObject *o);
...
}
複製代碼
咱們先看一下該函數的文檔說明:get
遞歸的搜索 object 及其子對象, 若是發現符合如下格式的槽函數, 則會自動鏈接
void on_<object name>_<signal name>(<signal parameters>);
複製代碼
舉個例子: 若是有一個子對象, 其類型爲 QPushButton, object name 爲 button1, 要關聯該按鈕 clicked() 信號的槽函數簽名應該爲:
void on_button1_clicked();
複製代碼
咱們再看它是如何實現的:
void QMetaObject::connectSlotsByName(QObject *o)
{
if (!o)
return;
const QMetaObject *mo = o->metaObject();
Q_ASSERT(mo);
// list of all objects to look for matching signals including...
const QObjectList list =
o->findChildren<QObject *>(QString()) // all children of 'o'...
<< o; // and the object 'o' itself
/* [1] */
}
複製代碼
QObjectList list
中存儲了 o 及其全部子對象. 接下來看 "[1]"代碼.
// for each method/slot of o ...
for (int i = 0; i < mo->methodCount(); ++i) {
const QByteArray slotSignature = mo->method(i).methodSignature();
const char *slot = slotSignature.constData();
Q_ASSERT(slot);
// ...that starts with "on_", ...
if (slot[0] != 'o' || slot[1] != 'n' || slot[2] != '_')
continue;
// ...we check each object in our list, ...
bool foundIt = false;
/* [2] 遍歷對象列表, foundIt */
if (foundIt) {
// we found our slot, now skip all overloads
while (mo->method(i + 1).attributes() & QMetaMethod::Cloned)
++i;
} else if (!(mo->method(i).attributes() & QMetaMethod::Cloned)) {
// check if the slot has the following signature: "on_..._...(..."
int iParen = slotSignature.indexOf('(');
int iLastUnderscore = slotSignature.lastIndexOf('_', iParen-1);
if (iLastUnderscore > 3)
qWarning("QMetaObject::connectSlotsByName: No matching signal for %s", slot);
}
}
複製代碼
開始遍歷該對象的全部成員方法, 找到以on_
開頭的方法. 而後遍歷存儲全部對象的列表 list. (遍歷過程後面再說). 若是找到匹配的了, 就跳過該方法的重載函數. 若是未找到, 肯定該函數符合on_<objectName>_<signal>()
格式, 而後打印 warning 信息.
- 剛開始看到直接使用 slot[0], slot[1], slot[2] 的時候, 還很好奇, 若是函數名只有一個字母, 不就數組越界了嗎? 後來本身把函數簽名打出來, 發現其實不會的. slotSignature 獲取到的爲函數簽名, 即便函數名爲f, 其簽名也爲 "f()". 因此函數簽名至少是三個字符.
接下來就看 "[2]" 代碼. 看看在遍歷對象列表時都作了些什麼.
for(int j = 0; j < list.count(); ++j) {
const QObject *co = list.at(j);
const QByteArray coName = co->objectName().toLatin1();
// ...discarding those whose objectName is not fitting the pattern "on_<objectName>_...", ...
if (coName.isEmpty() || qstrncmp(slot + 3, coName.constData(), coName.size()) || slot[coName.size()+3] != '_')
continue;
const char *signal = slot + coName.size() + 4; // the 'signal' part of the slot name
// ...for the presence of a matching signal "on_<objectName>_<signal>".
const QMetaObject *smeta;
int sigIndex = co->d_func()->signalIndex(signal, &smeta);
if (sigIndex < 0) {
QList<QByteArray> compatibleSignals;
const QMetaObject *smo = co->metaObject();
int sigLen = qstrlen(signal) - 1; // ignore the trailing ')'
for (int k = QMetaObjectPrivate::absoluteSignalCount(smo)-1; k >= 0; --k) {
const QMetaMethod method = QMetaObjectPrivate::signal(smo, k);
if (!qstrncmp(method.methodSignature().constData(), signal, sigLen)) {
smeta = method.enclosingMetaObject();
sigIndex = k;
compatibleSignals.prepend(method.methodSignature());
}
}
if (compatibleSignals.size() > 1)
qWarning() << "QMetaObject::connectSlotsByName: Connecting slot" << slot
<< "with the first of the following compatible signals:" << compatibleSignals;
}
if (sigIndex < 0)
continue;
// we connect it...
if (Connection(QMetaObjectPrivate::connect(co, sigIndex, smeta, o, i))) {
foundIt = true;
break;
}
}
複製代碼
遍歷對象列表, 找到符合本槽函數 object name 的對象. 而後查找該對象的是否有徹底符合 singnalName + 參數列表 的信號.
若是沒有找到, 則繼續查找至少知足該槽參數列表的信號. (由於信號的參數個數能夠大於槽參數個數). 若是多個信號都符合要求, 就按照信號在源文件中的聲明順序, 選擇第一個, 並打印一個 warning 信息.
以後, 將該信號與槽進行鏈接, 並退出該循環. 因此說, 若是存在相同 object name 的對象, 也只鏈接第一個. 其餘的都會被忽略. (Qt Designer 會確保 object name 的惟一性, 但其餘代碼添加的 object name 就不受控制了).