Создание пользовательских типов 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);
QString 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 и извлекать их позже. См. пример Примера пользовательского типа для кода, демонстрирующего это.
Макрос Q_DECLARE_METATYPE() также позволяет использовать эти значения в качестве аргументов сигналов, но только в прямых соединениях сигнал-слот. Чтобы сделать пользовательский тип общедоступным для механизма сигналов и слотов, нам нужно выполнить дополнительные действия.
Создание и уничтожение пользовательских объектов
Хотя объявление в предыдущем разделе делает тип доступным для использования в прямых соединениях сигнал-слот, его нельзя использовать для очередей соединений сигнал-слот, таких как те, которые создаются между объектами в разных потоках. Это связано с тем, что метаобъектная система не знает, как обрабатывать создание и уничтожение объектов пользовательского типа во время выполнения.
Чтобы разрешить создание объектов во время выполнения, вызовите шаблонную функцию qRegisterMetaType(), чтобы зарегистрировать его в метаобъектной системе. Это также делает тип доступным для очереди сообщений "сигнал-слот" при условии, что вы вызываете его до создания первого соединения, использующего этот тип.
Пример Примеры очередей пользовательских типов объявляет 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 типа в примере пользовательского типа прилагает усилия, чтобы сделать удобочитаемое представление максимально удобным для чтения:
QDebug operator<<(QDebug dbg, const Message &message)
{
const QString body = message.body();
QVector<QStringRef> pieces = body.splitRef("\r\n", QString::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() содержат более подробную информацию об их использовании и ограничениях.
Примеры Пользовательский тип и Тип очереди пользовательского типа показывают, как реализовать пользовательский тип с функциями, описанными в этом документе.
Документ Методы отладки предоставляет общий обзор механизмов отладки, обсуждавшихся выше.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.14/custom-types.html