Смотрю на форумах рунета, люди начинают писать на 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 очень удобные.
