Создание пользовательских типов Qt
Обзор
При создании пользовательских интерфейсов с Qt, особенно тех, что содержат специализированные элементы управления и функции, разработчикам иногда требуется создавать новые типы данных, которые могут использоваться наряду с или вместо существующего набора типов значений Qt.
Стандартные типы, такие как QSize, QColor и QString, могут храниться в объектах QVariant, использоваться в качестве типов свойств в классах, основанных на QObject, и передаваться в коммуникации «сигнал-слот».
В этом документе мы рассмотрим пользовательский тип и опишем, как интегрировать его в модель объектов Qt, чтобы он мог храниться так же, как стандартные типы Qt. Затем мы покажем, как зарегистрировать пользовательский тип, чтобы он мог использоваться в соединениях «сигнал-слот».
Создание пользовательского типа
Прежде чем начать, необходимо убедиться, что создаваемый пользовательский тип удовлетворяет всем требованиям, накладываемым QMetaType. Другими словами, он должен предоставлять:
- публичный конструктор по умолчанию,
- публичный конструктор копирования и
- публичный деструктор.
Следующее Message определение класса включает эти члены:
class Message
{
public:
Message();
Message(const Message &other);
~Message();
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()
{
thread = new RenderThread();
...
connect(thread, SIGNAL(sendBlock(Block)), this, SLOT(addBlock(Block)));
...
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/archives/qt-5.11/custom-types.html