Qt connectSlotsByName与信号槽

一 场景

  • 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()
      

五 注意

  1. 根据需要,connect 和 QMetaObject::connectSlotsByName 可以一同使用,以提高效率。

猜你喜欢

转载自blog.csdn.net/luoshabugui/article/details/109518874
今日推荐