问题引出:

在尝试实现《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设置好对象名

QMetaObject::connectSlotsByName: No matching signal for XXX 原理探究的更多相关文章

  1. QtCore.QMetaObject.connectSlotsByName:根据objectName和signal自动绑定slot

    from PyQt5.QtWidgets import (QWidget , QVBoxLayout , QHBoxLayout, QLineEdit, QPushButton) from PyQt5 ...

  2. qt QMetaObject::connectSlotsByName()自动关联失效问题解决

    自己编写qt程序的时候,想使用qt on_objectName_signalName()命名规则自动关联信号和槽,老是发现失效.多方求解,答案事实上很简单就是没有理解objectName的含义. on ...

  3. Qt 静态函数QMetaObject::connectSlotsByName(QObject * object)按命名规则自动connect,不需要手动connect

    看别人代码看到void on_MyWidget_slotTest(); 就郁闷了,没看到他代码里有connect 却能把信号和槽可以连接起来. 今日回顾书本发现该函所的nb之处. QMetaObjec ...

  4. Qt 静态函数QMetaObject::connectSlotsByName(QObject * object)

    看别人代码看到void on_MyWidget_slotTest(); 就郁闷了,没看到他代码里有connect 却能把信号和槽可以连接起来. 今日回顾书本发现该函所的nb之处. QMetaObjec ...

  5. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  6. [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化

    KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...

  7. [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化

    KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...

  8. [原] KVM 虚拟化原理探究 —— 目录

    KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...

  9. [原] KVM 虚拟化原理探究(4)— 内存虚拟化

    KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...

随机推荐

  1. java-servlet-cookie&sessions

    http协议是无状态协议  无状态协议的意思是服务端与客户端不会记录任何一次通信的信息 服务端"和"客户端",虽然见过很多面,但每次见面仍还是认不出对方,都是陌生人. 但 ...

  2. 学习zabbix(六)

    实验环境 实验用2到2台机器,实验所用机器系统环境如下,可以看到2台机器的主机名和IP地址 ? 1 2 3 4 5 6 7 8 9 10 [root@linux-node1 ~]# cat /etc/ ...

  3. Vue报错之"[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead......"

    一.报错截图 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the p ...

  4. 外部晶振的使用原因与内部RC振荡器的使用方法 _

    原因一 早些年,芯片的生产制作工艺也许还不能够将晶振做进芯片内部,但是现在可以了.这个问题主要还是实用性和成本决定的.   原因二 芯片和晶振的材料是不同的,芯片 (集成电路) 的材料是硅,而晶体则是 ...

  5. C#通过LDAP访问目录服务

    C#通过LDAP访问目录服务 本文介绍如何编写C#程序通过LDAP协议访问微软目录服务获得用户在目录中的属性信息.在开始部分先简单句介绍LDAP协议,然后是技术比较及实现部分. 目录 什么是LDAP? ...

  6. ECMAScript中有两种属性:数据属性和访问器属性。

    ECMA-262定义这些特性是为了实现JavaScript引擎用的,因此在JavaScript中不能直接访问它们.为了表示特性是内部值,该规范把它们放在了两对儿方括号中,例如 [[Enumerable ...

  7. js中DOM事件探究

    事件 纲要 理解事件流 使用事件处理程序 不同的事件类型 javascript和html的交互是通过事件实现的.事件就是文档或浏览器窗口发生的一些特定交互瞬间.可以使用侦听器(事件处理程序)预定事件, ...

  8. 前端文件上传-javascript-ajax

    书写是为了更好的记忆. 方案一:form表单上传 该方案优点是支持好,缺点刷新页面. <form action="url" method="post" e ...

  9. Linux 0.11源码阅读笔记-内存管理

    内存管理 Linux内核使用段页式内存管理方式. 内存池 物理页:物理空闲内存被划分为固定大小(4k)的页 内存池:所有空闲物理页组成内存池,以页为单位进行分配回收.并通过位图记录了每个物理页是否空闲 ...

  10. mapreduce分区

    本次分区是采用项目垃圾分类的csv文件,按照小于4的分为一个文件,大于等于4的分为一个文件 源代码: PartitionMapper.java: package cn.idcast.partition ...