Создание пользовательских типов 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 и извлекать их позже. См. пример 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);
Реализация для типа Message в Custom Type Example прилагает усилия, чтобы сделать отображаемое представление максимально читаемым:
QDebug operator<<(QDebug dbg, const Message &message)
{
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.maybeSpace();
} Выводимые данные в поток отладки, конечно, могут быть сделаны простыми или сложными, как вам угодно. Обратите внимание, что возвращаемый значением этой функции является сам объект QDebug, хотя это часто достигается вызовом члена функции maybeSpace() объекта QDebug, который заполняет поток пробелами, чтобы сделать его более читаемым.
Дополнительные материалы
Документация макроса Q_DECLARE_METATYPE() и функции qRegisterMetaType() содержит более подробную информацию об их использовании и ограничениях.
Примеры Custom Type и Queued Custom Type показывают, как реализовать пользовательский тип с функциями, описанными в этом документе.
Документ Debugging Techniques предоставляет обзор механизмов отладки, обсуждаемых выше.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.0/custom-types.html