Back to Home

Using Q_GADGET in C ++ & QtQuick

qt · qtquick

Using Q_GADGET in C ++ & QtQuick

    I look at the Runet forums, people start writing in C ++ & Qt Quick and use the QObject descendants for the so-called Value Type. Martin Fowler calls them Value Object . Although there is a Q_GADGET macro that allows you to use QMetaObject with some restrictions, but without inheriting from QObject. All that will be described below is the result of experiments with Qt Quick. I will be glad to learn something new from the comments.


    An example of such types are QPoint, QGeoCoordinate, etc. Inheriting from QObject and using the Q_OBJECT macro is inconvenient for these types:



    Q_GADGET allows us to use:


    • Q_ENUM ;
    • Q_PROPERTY ;
    • Q_INVOKABLE .

    Limitation:


    • Lack of support for signals and slots.

    If our application simply displays what came from the server, then we can create the structure:


    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 is used here to register the type in QVariant . Why is he needed here, more on that later.


    Such types can be used in the properties of other objects:


    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;
    }

    And we work quietly in js:


    var csize = crossword.size;
    //...
    rows = csize.rows();
    column = csize.column();

    Q_GADGET and Q_INVOKABLE


    For some reason, we cannot use ValueType in methods labeled Q_INVOKABLE . For that, you can return QVariant with ValueType! And also use it in js! This is very convenient in models, instead of many roles and 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 BucketModel::roleNames() const
    {
        static const QHash roles = {
            {Bucket, "bucket" }
        };
        return roles;
    };

    In the delegate as usual:


    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 and property


    Such types can be used as properties and bind to them. This is done through a generic type:


    Item {
        property var film
        //...
        Label {
            text: film.year
            //...
        }
        Label {
            text: film.countries
            //...
        }
      //...
    }

    Since before the instantiation type is unknown, the runtime complains (not falling) TypeError: Cannot read property 'year' of undefined.


    You can remove this abuse by initializing the property with some instance:


    QQmlApplicationEngine engine;
    Film film;
    engine.rootContext()->setContextProperty("emptyFilm", QVariant::fromValue(film));

    Item {
        property var film: emptyFilm
        //...
        Label {
            text: film.year
            //...
        }
        Label {
            text: film.countries
            //...
        }
     //...
    }

    It turns out to be very convenient when using StackView , on one screen you display a model with a minimum of information, and on the next screen in more detail:




    In my personal opinion, such a value type is very convenient.

    Read Next