Qt信号和槽,与const char* 的故事

Qt信号和槽,与const char* 的故事

本文定位:不追究Qt元对象系统(meta-object system)背后的东西,只涉及平时编程中看得到摸得着的表象。只看表象,不看原因。

SIGNAL 和 SLOT

非常常用的两个宏,配合connect使用。可是,你看过connect的声明么?

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection ) [static]

在 SIGNAL 和 SLOT 本该出现的地方,函数中是 const char *,即传统的 C 字符串。

想知道怎么不看源码就知道 SIGNAL 和 SLOT 在做什么吗?随便找个你的程序,加入下面的语句看看那输出即可:

qDebug(SIGNAL(YouCanPutAnyString...));
qDebug()<<SIGNAL(YouCanPutAnyString...);

注意,connect 中的那两个参数只是 const char*

无论直接放什么字符串进去(不用宏的话),

无论你在这两个SIGNAL和SLOT宏中写什么(用宏的话)

编译都不会出错(因为语法没问题,参数类型一样啊)。但运行时会提示你为何失败

信号或槽的参数带有命名空间或类的作用域

有没有遇到这样的问题(当然,这种设计风格时有问题的,破坏封装性,我们这儿暂不考虑它的坏处):

  • 要发射一个信号,可是它的参数类型是在其他命名空间或类中定义的

class B: public QObject
{
...
signals:
void emitValue(A::ValueType v);
}

看个完整的例子(保存为main.cpp,编译后即可运行):

#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QMetaType>

class B;
class A:public QObject
{
Q_OBJECT
public:
enum ValueType{E1, E2, E3};
A(QObject* parent=0);

public slots:
void setValue(ValueType v)
{}
private:
B * m_b;
};

class B:public QObject
{
Q_OBJECT
public:
B(QObject* parent=0):QObject(parent){}
signals:
void emitValue(A::ValueType v);
};

A::A(QObject* parent):QObject(parent),m_b(new B(this))
{
bool c = connect(m_b, SIGNAL(emitValue(A::ValueType)),SLOT(setValue(ValueType)));
qDebug()<<c;
}

#include "main.moc"

int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
A a;
return app.exec();
}

代码很简单:B 中的信号参数是 A中的 enum 类型 ValueType; A中有一个接收该类型参数的槽。程序编译也没问题。只是,会发生什么呢?

QObject::connect: Incompatible sender/receiver arguments
B::emitValue(A::ValueType) --> A::setValue(ValueType)
connect: failed

除了我们根据返回值得到的消息,Qt还提供了详细了的信息。告诉我们信号和槽的参数不匹配!!

怎么回事
  • 如果先前的实验你做过了,就会知道 SIGNAL 和 SLOT 只是把括号中的东西变成了字符串
connect(m_b, "2emitValue(A::ValueType)","1setValue(ValueType))")
  • connect 判断参数是否一致的方式就是:把括号的所谓类型(字符串)提取出来,然后比较两个字符串是否一致。很显然,

"A::ValueType" 不等于 "ValueType"
  • 于是 connect 失败
如何解决?尝试一

既然它只是比较字符串,你可能觉得在connect中改成一样的不就行了?

connect(m_b, SIGNAL(emitValue(A::ValueType)),SLOT(setValue(A::ValueType)))

或者

connect(m_b, SIGNAL(emitValue(ValueType)),SLOT(setValue(ValueType)))

确实,这样一改,connect 没有刚才那个错误了,但是:

Object::connect: No such slot A::setValue(A::ValueType) in main.cpp:32
connect: failed

或者

Object::connect: No such signal B::emitValue(ValueType) in main.cpp:32
connect: failed

啊?为什么这样,

connect中的信号和槽参数是一样了,但是却和类中定义的槽和信号不一样了,于是他抱怨:找不到槽或信号。

怎么回事二

我们都知道,要定义信号和槽,需要使用 Q_OBJECT(其实不是必须的,比如QAxObject和QAxWidget就没用它),然后slots,signals定义信号和槽。

问题就在这儿了,其实还是字符串的问题。Qt中的moc程序,扫描带Q_OBJECT的类,看到slots和signals后,将其提取并变成字符串,放到生成的 moc_xxx.cpp 或 xxx.moc 文件中。

考虑这儿的问题:

  • moc 提取槽时看到的是:
void setValue(ValueType v
  • 于是,它生成字符串 "setValue(ValueType)"

  • connect 根据它参数中的字符串,去找moc生成字符串
  • 结果没找到,于是抱怨:找不到参数中用的槽
如何解决?尝试二

现在你应该知道如何解决了。定义槽时就直接加上作用域

public slots:
void setValue(A::ValueType v

或者定义信号时不加作用域

typedef A::ValueType ValueType;
signals:
void emitValue(ValueType v)

好了,问题解决。我们接着扩展一下(不然故事多没劲啊)。

继续

我们知道connect最后一个参数是 Qt::AutoConnection,在我们这种情况下,自动就是 Qt::DirectConnection。那,想一想:如果我们把它改为Qt::QueuedConnection 会怎么样?

connect(m_b, SIGNAL(emitValue(A::ValueType)),SLOT(setValue(A::ValueType)),Qt::QueuedConnection)

这样一来,其实Qt的事件系统、元对象系统都搅和一块了,同样,我们这儿不考虑底层的东西。看看输出:

QObject::connect: Cannot queue arguments of type 'A::ValueType'
(Make sure 'A::ValueType' is registered using qRegisterMetaType().)
connect: failed 怎么回事

我们前面说了,connect 的参数是字符串,其中的类型也是字符串。但这儿,因为用的Qt::QueuedConnection连接方式。

  • 发送信号时,其实生成一个事件 QEvent
  • 事件中包含信号的信息
  • 接收者收到事件,开始通过元对象系统调用槽
  • 这时,它需要根据字符串"A::ValueType"去系统中找对应的类型(所以,它要先存在)

如何解决

答案connect已经给出了,调用这个connect语句前先用 qRegisterMetaType() 注册一下

qRegisterMetaType<ValueType>("A::ValueType");
//or
//qRegisterMetaType<A::ValueType>("A::ValueType");

qRegisterMetaType<ValueType>("ValueType");
//or
//qRegisterMetaType<A::ValueType>("ValueType");

一觉醒来,突然想起来,昨晚似乎有点东西忘写了,继续补全。--dbzhang800 20101031

normalized

前面一直再说字符串,字符串。可是,你想过没,如果信号的参数是 const QString&, 而槽的参数是与其语法完全等价的 QString const &。会怎么办?

异或中间再多加几个空格,还等价么?

SIGNAL(sig(const QString))
SLOT(slt(QString const ))

毕竟二者生成的字符串分别是:

"2sig(const QString)"
"1slt(QString const )"

如何直接比较的话,二者的参数显然不相同。

恩,和前面的作用域符问题不同,这个问题Qt是有充分考虑的,下面看看它是如何解决的。

Qt如何解决的

在connect函数的内部,它在比较参数之前,在查询信号和槽是否在对象中是否存在之前,稍稍做了一个归一化。调用的是:

QByteArray QMetaObject::normalizedSignature ( const char * method)[static]

该函数的作用呢,就是去掉字符串中多余的空白字符,并调整参数的顺序,甚至直接去掉 const。想知道如何验证么?很简单

qDebug()<<QMetaObject::normalizedSignature(SIGNAL(sig(const QString )));
qDebug()<<QMetaObject::normalizedSignature(SIGNAL(sig(QString const ))); 我们代码中会用到normalized么?

基本不会用到,但下面看一个用到它的例子。整个例子很简单,可能 connectNotify 函数你没用过(其实也不建议用)

当B的对象的信号 被connect到其他的槽或信号时,B对象的 connectNotify 函数会被调用,这样我们在b中就知道哪个信号被connect了。

#include <QtCore/QCoreApplication>
#include <QtCore/QMetaType>
#include <QtCore/QDebug>

class A:public QObject
{
Q_OBJECT
public:
A(){}

public slots:
void setValue(const QString& v){}
};

class B:public QObject
{
Q_OBJECT
public:
B(){}
signals:
void emitValue(QString const & v);
protected:
void connectNotify (const char * signal)
{
qDebug()<<signal;
}
};

#include "main.moc"

int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
A a;
B b;
QObject::connect(&b, SIGNAL(emitValue(QString const )), &a, SLOT(setValue(const QString)));
return app.exec();
}

很简单吧,运行后会输出代表信号的字符串

2emitValue(QString)

如果你想在这个 connectNotify 做点其他动作呢?比如对象有很多信号,只在该信号被连接时做动作,比较好的写法就是

void connectNotify (const char * signal)
{
if (signal == QMetaObject::normalizedSignature(emitValue(QString const ))
qDebug()<<signal;
}

这样你就不用担心,你提供的信号字符串和Qt内部的信号字符串不一致了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值