创建自定义 Qt 类型
概述
在使用 Qt 创建用户界面时,尤其是涉及专用控件和功能的界面,开发人员有时需要创建新的数据类型,这些类型可以与 Qt 现有的值类型并用,或直接替代它们。
诸如QSize 、QColor 和QString 等标准类型均可存储在QVariant 对象中,用作基于QObject 的类中的属性类型,并在信号-插槽通信中发出。
在本文档中,我们将以一个自定义类型为例,说明如何将其集成到 Qt 的对象模型中,使其能够像标准 Qt 类型一样被存储。随后,我们将演示如何注册该自定义类型,以便在信号与槽连接中使用它。
创建自定义类型
在开始之前,我们需要确保所创建的自定义类型满足QMetaType 规定的所有要求。换言之,它必须提供:
- 一个公有的默认构造函数,
- 一个公共的复制构造函数,以及
- 一个公用的析构函数。
以下Message 类定义包含这些成员:
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的类型系统将无法理解如何存储、检索和序列化该类的实例。例如,我们将无法将Message 的值存储在QVariant 中。
Qt 中负责自定义类型的类是QMetaType 。为了让该类识别该类型,我们需在定义该类的头文件中,对该类调用Q_DECLARE_METATYPE() 宏:
Q_DECLARE_METATYPE(Message);这样一来,Message 的值就可以存储在QVariant 对象中,并在以后进行检索:
QVariant stored;
stored.setValue(message);
...
Message retrieved=qvariant_cast<Message>(stored);
qDebug() << "Retrieved:" << retrieved;
retrieved=qvariant_cast<Message>(stored);
qDebug() << "Retrieved:" << retrieved;Q_DECLARE_METATYPE() 宏还允许将这些值用作信号的参数,但仅限于直接的信号-槽连接。为了使自定义类型能够普遍应用于信号和槽机制,我们需要进行一些额外的工作。
创建和销毁自定义对象
尽管上一节中的声明使得该类型可在直接信号-槽连接中使用,但它无法用于队列式信号-槽连接,例如不同线程中的对象之间建立的连接。这是因为元对象系统不知道如何在运行时处理该自定义类型对象的创建和销毁。
要启用运行时对象创建,请调用模板函数 `qRegisterMetaType()` 将其注册到元对象系统中。只要在建立第一个使用该类型的连接之前调用此函数,该类型即可用于队列式信号-插槽通信。
“队列式自定义类型”示例声明了一个Block 类,该类在main.cpp 文件中进行了注册:
intmain(intargc, char *argv[])
{
QApplication app(argc,argv);
...
qRegisterMetaType<Block>();
...
returnapp.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;这是通过为该类型创建一个流操作符来实现的,该操作符通常定义在该类型的头文件中:
此处针对Message 类型的实现,特意花了一些功夫,力求使其可打印表示尽可能易于阅读:
QDebug operator<<(QDebug dbg, const Message &message)
{
QDebugStateSaver saver(dbg);
const 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;
}当然,发送到调试流的输出可以随心所欲地设计得简单或复杂。请注意,该函数返回的值是QDebug 对象本身,尽管通常是通过调用QDebug 的maybeSpace()成员函数来获取该对象的,该函数会用空格字符填充流内容以提高可读性。
进一步阅读
Q_DECLARE_METATYPE() 宏和qRegisterMetaType() 函数的文档中包含有关其用法和限制的更详细信息。
“队列自定义类型”示例展示了如何实现具有本文所述功能的自定义类型。
《调试技术》文档概述了上述讨论的调试机制。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.