QMetaObject::connectSlotsByName: No matching signal for问题的解决方法

http://www.civilnet.cn/bbs/browse.php?topicno=4023

今天发现qt程序在运行时命令行窗口会弹出下面的警告:

QMetaObject::connectSlotsByName: No matching signal for ...

但实际功能并没有受影响。网上google了一圈,终于找到原因。

以下转自:http://www.qtforum.org/article/20685/connectslotsbyname.html

After a bit of sleuthing to find the source of all the Qt warnings, “QMetaObject::connectSlotsByName: No matching signal for …”, in our log window/file, here’s what I’ve found.

setupUi calls connectSlotsByName. connectSlotsByName parses the moc_ file in order to connect slots and signal s. The moc_ file contains a list of all the slots for the class. The parser iterates over the list of slot names looking for the following pattern: on_objectName_signal , where on_objectName_signal is the name of the slot, objectName is the object name and signal is the signal . For example, if you have a slot named, on_doneButton_clicked(), the parser looks for an object named doneButton, a signal named clicked and then connects the on_doneButton_clicked() slot to QButton’s signal clicked().

If you follow this naming convention, you do no t need to call the connect() method, no r do you need to connect the signal via the Qt UI editor in VisualStudio. connectSlotsByName will automatically make the connection for you.

So, in order to get rid of the “No matching signal for …” warnings, we need to either follow this naming convention, or make sure no ne of our slot names begin with “on_”. If you have a slot, onDoneButton_clicked, for example, connectSlotsByName will no try to connect it with a signal , no r will emit an warning. 

上面大概就是 说:用VistalStudio里的QtEditer可以自动调用Qt中“connectSlotsByName”即“按空间名称关联槽”的方式进行关 联,对应的函数必须写成“on_控件名_信号名”的格式;或者也可以通过connet函数人为显式地将信号和槽关联起来。但是,如果采用显式 connect的方法的同时,又将槽函数的名字起成了“on_控件名_信号名”的格式,那么就会在运行时弹出 “QMetaObject::connectSlotsByName: No matching signal for”的警告了!



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);
    }


然后编译运行,结果出来了:

9个按钮的objectName()都返回"TestButton" 
只有第一个按钮的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(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的实现,可以注意到以下几个地方:

第7行,取得o的所有子对象,在测试的代码里,QPushButton都设置了this为父对象,所以它们显然会在这个列表里出现 
第8行,是一个遍历o的方法的循环,o的信号和槽就在其中 
第11行,对于方法名称不是"on_"开头的方法跳过不处理,这也说明,如果你在一个QObject子类里定义了"on_"开头的槽的话,一定会被connectSlotsByName函数进行搜索匹配的操作的 
第14行开始到33行,开始遍历o的所有的子对象,试图匹配到与槽名称以及信号名称相应的子对象。首先取出其objectName()与槽名称里的第一个‘_’和第二个‘_’做名称匹配。其次取出子对象的所有信号,与第二个‘_’之后部分做匹配。
如果匹配成功,则会执行36行的连接代码。连接成功的话,就会在38行break中断循环。 
看到第5点,已经很明了了,对于同名的控件,connectSlotsByName只会连接子对象链表里的第一个对象的信号到槽上。

总结:

尽量不要让QObject出现相同objectName的情况 
如果同名connectSlotsByName只能给其中一个建立缺省的信号和槽的连接 
如果出现大量编码创建大量控件的情况,最好是自己去建立信号和槽的连接,而不是依赖connectSlotsByName来做到这个工作。connectSlotsByName更适合的任务是与desinger配合完成缺省的信号和槽的连接。 
其他:

在测试过程中,曾经把ui->setupUi(this);放到了控件创建之前运行,结果运行时提示:

QMetaObject::connectSlotsByName: No matching signal for on_TestButton_clicked

从connectSlotsByName的代码可以看到这实际上执行的是第46行,如果在调试程序中遇到这样的信息,可以检查一下,是否是控件的objectName与你编写的槽里的objectName并不相符。

你可能感兴趣的:(Qt)