本文定位:不追究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 argumentsB::emitValue(A::ValueType) --> A::setValue(ValueType)
connect: failed
除了我们根据返回值得到的消息,Qt还提供了详细了的信息。告诉我们信号和槽的参数不匹配!!
怎么回事- 如果先前的实验你做过了,就会知道 SIGNAL 和 SLOT 只是把括号中的东西变成了字符串
- connect 判断参数是否一致的方式就是:把括号的所谓类型(字符串)提取出来,然后比较两个字符串是否一致。很显然,
- 于是 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:32connect: failed
或者
Object::connect: No such signal B::emitValue(ValueType) in main.cpp:32connect: failed
啊?为什么这样,
connect中的信号和槽参数是一样了,但是却和类中定义的槽和信号不一样了,于是他抱怨:找不到槽或信号。
怎么回事二我们都知道,要定义信号和槽,需要使用 Q_OBJECT(其实不是必须的,比如QAxObject和QAxWidget就没用它),然后slots,signals定义信号和槽。
问题就在这儿了,其实还是字符串的问题。Qt中的moc程序,扫描带Q_OBJECT的类,看到slots和signals后,将其提取并变成字符串,放到生成的 moc_xxx.cpp 或 xxx.moc 文件中。
考虑这儿的问题:
- moc 提取槽时看到的是:
-
于是,它生成字符串 "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内部的信号字符串不一致了。