概述
当使用Qt创建用户界面时,尤其是那些具有专门控件和功能的用户界面时,开发人员有时需要创建可以与Qt现有值类型集一起使用或者代替其使用的新数据类型
标准类型(比如QSize、QColor、QString
)都可以存储在QVariant对象中,用作基于QObject的类中的属性类型,并在信号插槽通信中发出
在本文档中,我们采用一种自定义类型,并描述如何将其集成到Qt的对象模型中,以便可以与标准Qt类型相同的方式存储它。然后,我们展示如何注册自定义类型,以允许其在信号和槽连接中使用
创建自定义类型
在开始之前,我们需要确保我们创建的自定义类型满足QMetaType强加的所有需求。换言之,它必须提供:
- 一个公共默认构造函数,
- 公共复制构造函数,以及
- 一个公共析构函数。
以下消息类定义包括以下成员:
class Message
{
public:
Message() = default;
~Message() = default;
Message(const Message &) = default;
Message &operator=(const Message &) = default;
Message(const QString &body, const QStringList &headers);
QStringView body() const;
QStringList headers() const;
private:
QString m_body;
QStringList m_headers;
};
该类还提供了一个正常使用的构造函数和两个用于获取私有数据的公共成员函数。
使用QMetaType声明
Message类只需要一个合适的实现就可以使用。然而,没有一些帮助,Qt的类型系统将无法理解如何存储、检索和序列化这个类的示例。比如,我们将无法在QVariant中存储消息值
Qt中负责定制类型的类是QMetaType。为了使该类知道该类型,我们在定义该类的头文件中的类上调用Q_DECLARE_METATYPE()宏:
Q_DECLARE_METATYPE(Message);
这样就可以将消息值存储在QVariant对象中并在以后检索。有关演示这一点的代码,请参阅自定义类型示例。
宏Q_DECLARE_METATYPE()
还可以将这些值用作信号的参数,但仅在直接信号槽连接中使用。为了使自定义类型能够和信号与槽机制一起使用,我们需要执行一些额外的工作
创建和销毁自定义对象
虽然前一节中的声明使该类型可用于直接信号槽连接,但不能用于队列信号槽连接,比如在不同线程中的对象之间创建的连接。这是因为元对象系统不知道如何在运行时处理自定义类型对象的创建和销毁
要在运行时创建对象,请调用qRegisterMetaType()
模版函数,将其注册到元对象系统中。这也使得该类型可用于排队信号槽通信,只要您在建立第一个使用该类型的连接之前调用它。
队列自定义类型示例声明了一个Block类,该类注册在main.cpp文件中:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
...
qRegisterMetaType<Block>();
...
return app.exec();
}
这种类型稍后会在window.cpp文件中的信号槽连接中使用:
Window::Window(QWidget *parent)
: QWidget(parent), thread(new RenderThread(this))
{
...
connect(thread, &RenderThread::sendBlock,
this, &Window::addBlock);
...
setWindowTitle(tr("Queued Custom Type"));
}
如果在未注册的队列连接中使用了某个类型,则会在控制台打印一个警告;例如:
QObject::connect: Cannot queue arguments of type 'Block'
(Make sure 'Block' is registered using qRegisterMetaType().)
使类型可打印
为了调试目的,让自定义类型成为可打印的通常是非常有用的,如下面的代码所示:
Message message(body, headers);
qDebug() << "Original:" << message;
这可以通过为该类型创建一个流操作符来实现,该操作符通常在该类型的头文件中定义:
QDebug operator<<(QDebug dbg, const Message &message)
{
QList<QStringView> pieces = message.body().split(u"\r\n", Qt::SkipEmptyParts);
if (pieces.isEmpty())
dbg.nospace() << "Message()";
else if (pieces.size() == 1)
dbg.nospace() << "Message(" << pieces.first() << ")";
else
dbg.nospace() << "Message(" << pieces.first() << " ...)";
return dbg.maybeSpace();
}
当然,发送到调试流的输出可以根据您的喜好简单或复杂。注意,这个函数返回的值是QDebug对象本身,尽管这通常是通过调用QDebug的mayspace()成员函数获得的,该函数用空格字符填充流,使其更易于阅读。
扩展
Q_DECLARE_METATYPE()宏和qRegisterMetaType()函数文档包含有关它们的使用和限制的更详细信息。
自定义类型和排队自定义类型示例展示了如何使用本文档中概述的特性实现自定义类型。
调试技术文档提供了上面讨论的调试机制的概述。