创建自定义Qt类型

概述

使用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);

     QString 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对象中并在以后检索。有关演示此功能的代码,请参见自定义类型示例( Custom Type Example)。

Q_DECLARE_METATYPE()宏还可以将这些值用作信号的参数,但仅在直接的信号槽连接中使用。要使自定义类型与信号和插槽机制一起普遍可用,我们需要执行一些额外的工作。

创建和销毁自定义对象

尽管上一节中的声明使该类型可用于直接的信号插槽连接,但它不能用于排队的信号插槽连接,例如在不同线程中的对象之间建立的信号插槽连接。这是因为元对象系统不知道如何在运行时处理自定义类型对象的创建和销毁。

在运行时创建对象,可以调用qRegisterMetaType()模板函数将其注册到元对象系统中。这也使得该类型可用于队列信号插槽通信,只要您在创建使用该类型的第一个连接之前调用该类型。

这个排队自定义类型(Queued Custom Type Example)的例子声明了一个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);

自定义类型示例中的消息类型的实现努力使可打印的表示形式尽可能具有可读性:

QDebug operator<<(QDebug dbg, const Message &message)
 {
     const QString body = message.body();
     QVector<QStringRef> pieces = body.splitRef(QLatin1String("\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的maybeSpace()成员函数获得的,该函数用空格字符填充流,以使其更具可读性。

进一步阅读

Q_DECLARE_METATYPE()宏和qRegisterMetaType()函数文档包含关于它们的使用和限制的更详细的信息。

定义类型(Custom Type Example )和排队自定义类型示例(Queued Custom Type Example )展示了如何使用本文档中概述的特性实现自定义类型。

试技术(Debugging Techniques)文档提供了上面讨论的调试机制的概述。