警告

本节包含从C++自动翻译到Python的代码片段,可能包含错误。

创建自定义Qt类型

如何使用Qt创建和注册新类型。

概述

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

标准类型如QSize、QColor和QString都可以存储在QVariant对象中,用作基于QObject的类中的属性类型,并在信号-槽通信中发出。

在本文档中,我们采用一个自定义类型,并描述如何将其集成到Qt的对象模型中,以便它可以像标准的Qt类型一样存储。然后我们展示如何注册自定义类型,以允许其在信号和槽连接中使用。

创建自定义类型

在我们开始之前,我们需要确保我们创建的自定义类型满足QMetaType 所施加的所有要求。换句话说,它必须提供:

  • 一个公共的默认构造函数,

  • 一个公共的复制构造函数,以及

  • 一个公共的析构函数。

以下 Message 类定义包括这些成员:

class Message():

# public
    Message() = default
    ~Message() = default
    Message(Message ) = default
    Message operator=(Message ) = default
    Message(QString body, QStringList headers)
    body = QStringView()
    headers = QStringList()
# private
    m_body = QString()
    m_headers = QStringList()

该类还提供了一个用于正常使用的构造函数和两个用于获取私有数据的公共成员函数。

使用QMetaType声明类型

Message 类只需要一个合适的实现即可使用。然而,如果没有一些帮助,Qt 的类型系统将无法理解如何存储、检索和序列化此类的实例。例如,我们将无法在 QVariant 中存储 Message 值。

在Qt中负责自定义类型的类是QMetaType。为了让这个类知道该类型,我们在定义该类的头文件中调用Q_DECLARE_METATYPE()宏:

Q_DECLARE_METATYPE(Message)

现在这使得Message值可以存储在QVariant对象中并在以后检索:

stored = QVariant()
stored.setValue(message)            ...

retrieved = Message(stored)
print("Retrieved:", retrieved)
retrieved = Message(stored)
print("Retrieved:", retrieved)

Q_DECLARE_METATYPE() 宏还使得这些值可以作为信号的参数使用,但仅限于直接信号-槽连接。为了使自定义类型在信号和槽机制中普遍可用,我们需要进行一些额外的工作。

创建和销毁自定义对象

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

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

Queued Custom Type 示例声明了一个 Block 类,该类在 main.cpp 文件中注册:

qRegisterMetaType<Block>()
window = Window()
window.show()
window.loadImage(createImage(256, 256))
sys.exit(app.exec())            ...

qRegisterMetaType<Block>()            ...

    sys.exit(app.exec())

if __name__ == "__main__":

    app = QApplication([])
    qRegisterMetaType<Block>()

这种类型稍后会在window.cpp文件中的信号-槽连接中使用:

def __init__(self, parent):
    super().__init__(parent)
    self.thread = RenderThread(self)            ...

thread.sendBlock.connect(
        self.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)
print("Original:", message)

这是通过为该类型创建一个流操作符来实现的,该操作符通常在该类型的头文件中定义:

operator<< = QDebug(QDebug dbg, Message message)

这里对Message类型的实现做了一些努力,以使可打印的表示尽可能易读:

QDebug operator<<(QDebug dbg, Message message)

    pieces = message.body().split(u"\r\n", Qt.SkipEmptyParts)
    if pieces.isEmpty():
        dbg.nospace() << "Message()"
    elif pieces.size() == 1:
        dbg.nospace() << "Message(" << pieces.first() << ")"
else:
        dbg.nospace() << "Message(" << pieces.first() << " ...)"
    return dbg

发送到调试流的输出当然可以根据您的喜好变得简单或复杂。请注意,此函数返回的值是QDebug对象本身,尽管这通常是通过调用QDebugmaybeSpace()成员函数来获得的,该函数用空格字符填充流以使其更易读。

进一步阅读

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

Queued Custom Type 示例展示了如何实现一个自定义类型,该类型具有本文档中概述的功能。

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