警告

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

创建自定义 Qt 类型#

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

概述#

在用 Qt 创建用户界面,尤其是包含特殊控件和功能时,开发者有时需要创建新的数据类型,这些类型可以与 Qt 的现有值类型一起使用或替换它们。

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

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

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

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() 函数的文档包含了它们的使用和限制的更详细信息。

排队自定义类型 示例说明了如何实现本文档中概述的特性。

调试技巧文档提供了上述调试机制的概述。