一 场景
- 在 Qt connect与信号槽 一文中提到,信号槽主要通过 connect 进行连接,并且有好几种不同的写法。
- 新建一个Demo Widgets 程序。在 Qt 设计师界面中,拖入一个 QPushButton 控件,在其上右键 - 转到槽 - 选择信号 clicked(),会自动生成一个槽函数 on_pushButton_clicked()。发现点击按钮会触发该槽,但在代码中并没有找到 connect 的存在。也就是说 信号槽的连接 还存在其他的方式。
二 setupUi
-
探究代码,在主界面类的构造函数中,除了 setupUi 函数,并没有其他特殊的地方。setupUi 如下:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); }
-
展开 setupUi
...... class Ui_MainWindow { public: QWidget *centralWidget; QPushButton *pushButton; QMenuBar *menuBar; QToolBar *mainToolBar; QStatusBar *statusBar; void setupUi(QMainWindow *MainWindow) { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName(QStringLiteral("MainWindow")); MainWindow->resize(400, 300); centralWidget = new QWidget(MainWindow); centralWidget->setObjectName(QStringLiteral("centralWidget")); pushButton = new QPushButton(centralWidget); pushButton->setObjectName(QStringLiteral("pushButton")); pushButton->setGeometry(QRect(140, 80, 75, 23)); MainWindow->setCentralWidget(centralWidget); menuBar = new QMenuBar(MainWindow); menuBar->setObjectName(QStringLiteral("menuBar")); menuBar->setGeometry(QRect(0, 0, 400, 23)); MainWindow->setMenuBar(menuBar); mainToolBar = new QToolBar(MainWindow); mainToolBar->setObjectName(QStringLiteral("mainToolBar")); MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); statusBar = new QStatusBar(MainWindow); statusBar->setObjectName(QStringLiteral("statusBar")); MainWindow->setStatusBar(statusBar); retranslateUi(MainWindow); QMetaObject::connectSlotsByName(MainWindow); // 注意此处 } // setupUi ......
- 在 setupUi 中唯一的特殊之处为调用 QMetaObject::connectSlotsByName。
三 connectSlotsByName
-
在 Qt 助手中搜索该函数,描述如下:
Searches recursively for all child objects of the given object, and connects matching signals from them to slots of object that follow the following form:
void on_<object name>_<signal name>(<signal parameters>);
Let’s assume our object has a child object of type QPushButton with the object name button1. The slot to catch the button’s clicked() signal would be:
void on_button1_clicked();
If object itself has a properly set object name, its own signals are also connected to its respective slots.See also QObject::setObjectName().
-
简单来说,按照规则书写槽函数的名称,并且调用 QMetaObject::connectSlotsByName, 能够自动将子对象的信号与槽进行连接。
-
举例
// mainwindow.h ...... private slots: void on_btn_clicked(); ...... // mainwindow.cpp ...... QPushButton* button = new QPushButton(this); button->setObjectName(QStringLiteral("btn")); qDebug() << button->objectName(); QMetaObject::connectSlotsByName(this); ......
- 按照 on_< objectName >_< signal > 形式创建槽函数名称。
- 在代码中直接调用 QMetaObject::connectSlotsByName,可以实现信号槽连接。
四 源码
-
QMetaObject::connectSlotsByName 的实现在源文件 qobject.cpp中,如下:
void QMetaObject::connectSlotsByName(QObject *o) { if (!o) return; const QMetaObject *mo = o->metaObject(); Q_ASSERT(mo); const QObjectList list = // list of all objects to look for matching signals including... o->findChildren<QObject *>(QString()) // all children of 'o'... << o; // and the object 'o' itself // 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; 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) { // if no exactly fitting signal (name + complete parameter type list) could be found // look for just any signal with the correct name and at least the slot's parameter list. // Note: if more than one of thoses signals exist, the one that gets connected is // chosen 'at random' (order of declaration in source file) 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; // ...and stop looking for further objects with the same name. // Note: the Designer will make sure each object name is unique in the above // 'list' but other code may create two child objects with the same name. In // this case one is chosen 'at random'. break; } } 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); } } }
-
过程:
- findChildren 获取对象 o 所有子对象存入 list;
- 遍历对象 o 的所有方法,并仅对 on_< objectname >_< signal > 形式的方法进行处理;
- 遍历子对象,先找到 objectname 匹配的子对象,然后 通过 signalIndex 查找该子对象完全匹配的信号
- 若未找到完全匹配信号(fitting signal : name + complete parameter type list),则遍历该子对象的信号,查找兼容匹配信号(compatible signal : any signal with the correct name and at least the slot’s parameter list.)
- 找到信号,则通过 QMetaObjectPrivate::connect 进行连接。
-
注意
-
若查到到多个匹配信号,只会对第一个信号进行连接;
-
未找到信号,则会有 No matching signal 提示信息,注意观察。
// 将 on_pushButton_clicked 更改为 on_pushButton1_clicked 或 on_pushButton_clicked1 // 会出现以下输出信息: QMetaObject::connectSlotsByName: No matching signal for on_pushButton1_clicked() QMetaObject::connectSlotsByName: No matching signal for on_pushButton_clicked1()
-
五 注意
- 根据需要,connect 和 QMetaObject::connectSlotsByName 可以一同使用,以提高效率。