Смотрю на форумах рунета, люди начинают писать на C++&Qt Quick и используют наследников от QObject, для так называемых типов значений(Value Type). Мартин Фаулер их называет Value Object. Хотя есть макрос Q_GADGET позволяющий использовать QMetaObject c некоторыми ограничениями, но без наследования от QObject. Все что будет описано ниже результат экспериментов с Qt Quick. Буду рад узнать что-то новое из комментариев.
Пример таких типов QPoint, QGeoCoordinate и т.д. Наследоваться от QObject и использовать макрос Q_OBJECT неудобно для таких типов:
- QObject защищен от копирования;
- нужно возвращать значение по указателю. Приходится задумываться о CppOwnership/JavaScriptOwnership из перечисления QQmlEngine::ObjectOwnership.
Q_GADGET позволяет нам использовать:
- Q_ENUM;
- Q_PROPERTY;
- Q_INVOKABLE.
Ограничение:
- Отсутствие поддержки сигналов и слотов.
Если наше приложение просто отображает то, что пришло с сервера, то можно завести структуру:
struct PlayItem
{
private:
Q_GADGET
Q_PROPERTY(int episode MEMBER episode)
Q_PROPERTY(QString mp4Url MEMBER mp4Url)
Q_PROPERTY(QString name MEMBER name)
public:
int episode;
QString mp4Url;
QString name;
static PlayItem fromJson(const QJsonObject& jobj);
};
Q_DECLARE_METATYPE(PlayItem)
Q_DECLARE_METATYPE здесь используется для регистрации типа в QVariant. Зачем он тут нужен, об этом будет позже.
Такие типы можно использовать в свойствах других объектов:
class Size
{
Q_GADGET
public:
Q_INVOKABLE quint16 rows() const noexcept;
Q_INVOKABLE quint16 column() const noexcept;
Q_INVOKABLE bool isNull() const noexcept;
//..
};
class Crossword: public QObject
{
Q_OBJECT
Q_PROPERTY(Size size READ size)
public:
Crossword(QObject* parent = nullptr);
Size size() const noexcept;
}
И спокойно работаем в js:
var csize = crossword.size;
//...
rows = csize.rows();
column = csize.column();
Q_GADGET и Q_INVOKABLE
Почему то мы не можем использовать ValueType в методах помеченными Q_INVOKABLE. За то можно возвращать QVariant с ValueType! И так же использовать его в js! Это очень удобно в моделях, заместо множества ролей и switch:
QVariant BucketModel::data(const QModelIndex &index, int role) const
{
switch (role)
{
case Bucket:
return QVariant::fromValue(m_buckets[index.row()]);
default:
return QVariant();
}
}
QHash<int, QByteArray> BucketModel::roleNames() const
{
static const QHash<int, QByteArray> roles = {
{Bucket, "bucket" }
};
return roles;
};
В делегате как обычно:
delegate: ItemDelegate {
width: parent.width
text: bucket.name
Image{
visible: bucket.id === b2App.settings.bucketId
anchors{
right:parent.right
verticalCenter: parent.verticalCenter
margins: 8
}
source: "qrc:/icons/tick/tick.png"
}
Item и property
Такие типы можно использовать как свойства и делать на них привязки. Это осуществляется через общий тип(generic type):
Item {
property var film
//...
Label {
text: film.year
//...
}
Label {
text: film.countries
//...
}
//...
}
Так как до инстанцирования тип неизвестен, то во время выполнения ругается(но не падает): TypeError: Cannot read property 'year' of undefined
.
Убрать эту ругань можно инициализировав свойство, каким-нибудь экземпляром:
QQmlApplicationEngine engine;
Film film;
engine.rootContext()->setContextProperty("emptyFilm", QVariant::fromValue(film));
Item {
property var film: emptyFilm
//...
Label {
text: film.year
//...
}
Label {
text: film.countries
//...
}
//...
}
Это оказывается очень удобно, когда используется StackView, на одном экране выводишь модель с минимум информацией, а на следующем экране более подробно:
По-моему личному мнению, такие value type очень удобные.
Автор: RPG18