创建自定义 Qt 类型

概述

在使用 Qt 创建用户界面时,特别是那些具有专用控件和功能的情况,开发人员有时需要创建新数据类型,这些类型可以与 Qt 现有的值类型一起使用或替代。

QSizeQColorQString 这样的标准类型都可以存储在 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 的类型系统将无法了解如何存储、检索和序列化此类的实例。例如,我们将无法在 QVariant 中存储 Message 值。

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() 模板函数,将其与元对象系统注册。这也会使类型在使用此类类型建立第一个连接之前,可用于排队的信号-槽通信。

排队的自定义类型 示例声明了一个在 main.cpp 文件中注册的 Block

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

此处对 Message 类型实现的实现不惜一切努力来使可打印表示尽可能易读

QDebug operator<<(QDebug dbg, const Message &message)
{
    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 对象本身,尽管这通常是通过调用 maybeSpace() 成员函数获得的,该函数通过填充空格字符来使流更易读。

进一步阅读

Q_DECLARE_METATYPE() 宏和 qRegisterMetaType() 函数的文档包含有关其用途和限制的更多详细信息。

排队的自定义类型 示例展示了如何实现具有此文档中概述的特性自定义类型。

调试技术 文档提供了上述讨论的调试机制的概述。

© 2024 Qt 公司有限公司。包含在此处的文档贡献是由各自所有者的版权所有。提供的文档是根据自由软件基金会发布的 GNU自由文档许可证版本1.3 的条款授予的许可。Qt和相应的标志是芬兰和/或其他国家的Qt公司的商标。所有其他商标均为其各自所有者的财产。