QMetaObject::connectSlotsByName: No matching signal for XXX 原理探究

问题引出:

在尝试实现《Qt5.9 c++开发指南》混合UI编程章节时,用纯代码形式实现了个小按钮,然后加了个对应的槽函数,运行时就提示了这个信息。

原因探究:

首先查阅官方手册中的说明:

[static] void QMetaObject::connectSlotsByName(QObject *object)

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

简单来讲就是调用connectSlotsByName(obj)的时候会将obj里所有的槽函数(即形如on_XXX_XXX的函数)与obj的子对象(或者说是obj里的控件)尝试连接,如果有槽函数没有连接到对应的子对象,就会产生警告信息

这就产生了疑问:
在源代码中我们并没有调用过connectSlotsByName,为什么会产生警告?

细看的话书里讲过这个问题,首先我们创建一个QT默认的widget项目,一路yes下去,最后得到一个像下图这样的简单项目
打开mainwindow.h文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

可以发现里面声明了两个MainWindow类,其中一个放在Ui命名空间下,另一个没有命名空间。
再打开MainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QMetaObject::connectSlotsByName(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

可以发现里面只实现了MainWindow类,并没有实现Ui::MainWindow类,但是它却new了一个Ui::MainWindow,这证明Ui::MainWindow应在别处有定义。
使用qt creater自带的查找引用功能可以发现Ui::MainWindow存在于ui_mainwindow.h中,这个文件是qt creater根据mainwindow.ui编译而来的,以下为ui_mainwindow.h的内容:

#include 。。。。。 //省略各种头文件声明

QT_BEGIN_NAMESPACE

class Ui_MainWindow
{
public:
    QWidget *centralwidget;
    QMenuBar *menubar;
    QStatusBar *statusbar;

    void setupUi(QMainWindow *MainWindow)
    {
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QStringLiteral("MainWindow"));
        MainWindow->resize(800, 600);
        centralwidget = new QWidget(MainWindow);
        centralwidget->setObjectName(QStringLiteral("centralwidget"));
        MainWindow->setCentralWidget(centralwidget);
        menubar = new QMenuBar(MainWindow);
        menubar->setObjectName(QStringLiteral("menubar"));
        MainWindow->setMenuBar(menubar);
        statusbar = new QStatusBar(MainWindow);
        statusbar->setObjectName(QStringLiteral("statusbar"));
        MainWindow->setStatusBar(statusbar);

        retranslateUi(MainWindow);

        QMetaObject::connectSlotsByName(MainWindow);
    } // setupUi

    void retranslateUi(QMainWindow *MainWindow)
    {
        MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", Q_NULLPTR));
    } // retranslateUi

};

namespace Ui {
    class MainWindow: public Ui_MainWindow {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_MAINWINDOW_H

在底下可以看到Ui::MainWindow类直接继承自Ui_MainWindow类。而Ui_MainWindow中的setupUi占了大量篇幅,可以看出它的主要功能是实现将UI文件里的布局以代码化形式实现出来,在setupUi最后一行可以看到它调用了QMetaObject::connectSlotsByName。

回到MainWindow.h,我们可以看到它构造函数第一行调用的就是setupUi,这就是说在UI文件绘制到屏幕上的那一刻就已经调用了connectSlotsByName,而此时我们自己手写的添加的控件的代码和槽函数的代码还没有被执行,所以槽函数此时还连接不到对象,故弹出警告。

源码分析:

我们打开connectSlotByName的源代码慢慢研究:

void QMetaObject::connectSlotsByName(QObject *o)
{
    if (!o)
        return;
    const QMetaObject *mo = o->metaObject();
    Q_ASSERT(mo);
    //把o的所有子对象存进list里 
    const QObjectList list = 
            o->findChildren<QObject *>(QString()) 
            << o; 

    // 遍历o的所有方法 
    for (int i = 0; i < mo->methodCount(); ++i) {
        const QByteArray slotSignature = mo->method(i).methodSignature();
        const char *slot = slotSignature.constData();
        Q_ASSERT(slot);

        // 跳过不以“on_”开头的方法 
        if (slot[0] != 'o' || slot[1] != 'n' || slot[2] != '_')
            continue;

        //遍历list里的子对象,尝试让它们连接当前方法
        bool foundIt = false;
        for(int j = 0; j < list.count(); ++j) {
            const QObject *co = list.at(j);
            const QByteArray coName = co->objectName().toLatin1();

            // 跳过格式不是"on_<当前遍历对象名>_..."的方法 
            if (coName.isEmpty() || qstrncmp(slot + 3, coName.constData(), coName.size()) || slot[coName.size()+3] != '_')
                continue;

            const char *signal = slot + coName.size() + 4; // 字符指针signal指向函数名中信号段开始位置 

            // ...for the presence of a matching signal "on_<objectName>_<signal>".
            const QMetaObject *smeta;
			// 获取名字与当前signal字符串内容相同并且变量列表也相同的信号的编号,找不到返回-1 
            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);
                    //放宽要求,只要方法名和signal一样就行,变量列表不同没关系 
                    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;

            // 找到了就尝试连接,连接成功就停止 
            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) {
            //找到方法对应的子对象,就跳过剩下的 
            while (mo->method(i + 1).attributes() & QMetaMethod::Cloned)
                  ++i;
        } else if (!(mo->method(i).attributes() & QMetaMethod::Cloned)) {
            // 如果到头来形如 "on_..._...(..."的方法还是没有找到对象,就输出一个警告 
            int iParen = slotSignature.indexOf('(');
            int iLastUnderscore = slotSignature.lastIndexOf('_', iParen-1);
            if (iLastUnderscore > 3)
                qWarning("QMetaObject::connectSlotsByName: No matching signal for %s", slot);
        }
    }
}

研究后可以到No matching signal for 的触发条件为:
on_<objectName>_<signalName>形式的函数并且找不到对象名为objectName的对象

解决方案:

方案一:
1.更改函数名,避免用on_<objectName>_<signalName>
2.手动用connect连接对象与槽函数

方案二:
在调用setUi函数之前就new好对象,并且调用它的setObjectName设置好对象名

posted @ 2020-01-02 15:25  康宇PL  阅读(828)  评论(0编辑  收藏  举报