Wie erstellt man ein generisches Objektmodell zur Verwendung in QML?

Lesezeit: 7 Minuten

Wie erstellt man ein generisches Objektmodell zur Verwendung in QML
Ludek Wodika

Ich würde gerne wissen, ob es ein Makro oder eine Möglichkeit gibt, das Qt-Modell als Eigenschaft von QObject zu registrieren.

Ich habe zum Beispiel AnimalModel (http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel).

Ich weiß, dass ich es an den Stammkontext von QuickView übergeben kann

QuickView view;
view.rootContext()->setContextProperty("myModel", &model);

Falls ich QObject über Qml-Makros registriert habe, kann ich dieses Objekt auch zur Ansicht übergeben:

view.rootContext()->setContextProperty("obj", pDataObject);

Aber was ist, wenn ich QObject haben möchte, das ein Modell aller Daten enthält?

Zum Beispiel:

class DataObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
    ...

    AnimalModel m_modelAnimals;

    //Is this possible in any way?
    //Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)
};

Jedes Beispiel, das ich bis jetzt gefunden habe, zeigt, wie man besteht QAbstractListModel zum Wurzelkontext. Aber keine, wie man es als QObject-Eigenschaft verwendet.

(Ich weiß, es gibt QQmlListProperty aber QQmlListProperty unterstützt keine teilweise Aktualisierung. Es ist immer notwendig, alle Qml-Objekte neu zu erstellen)

Wie erstellt man ein generisches Objektmodell zur Verwendung in QML
dtech

//Is this possible in any way?
//Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)

Ja, hast du es nicht versucht? Natürlich wird es keine sein AnimalModel aber ein AnimalModel *aber solange das Modell erbt QAbstractListModel, das ist alles, was Sie brauchen. Du brauchst die nicht einmal NOTIFY Teil, da Änderungen innerhalb des Modells ohnehin automatisch widergespiegelt werden. modelAnimalsChanged macht nur Sinn, wenn Sie das gesamte Modell durch ein anderes Modell ersetzen, und natürlich, um die Warnungen von QML zur Verwendung einer Eigenschaft ohne ein Notify-Signal zum Schweigen zu bringen. Eine sauberere Möglichkeit, Letzteres zu tun, wenn sich das Modellobjekt nicht ändert, besteht darin, einfach a zurückzugeben AnimalModel * aus einem Slot oder a Q_INVOKABLE.

Wenn Sie ein wirklich flexibles Modell wünschen, können Sie eines herstellen, das sich speichern lässt QObject *, dann können Sie aus QML beliebige Objekte mit beliebigen Eigenschaften erstellen und dem Modell hinzufügen. Dann haben Sie aus dem Modell eine einzige object Rolle, die das Objekt zurückgibt, und Sie können das Objekt abfragen und verwenden, um die darin enthaltenen Eigenschaften abzurufen. Während eine “klassische” Listenmodellimplementierung ein Modell mit einem statischen, festen Schema definiert, ermöglicht die Verwendung dieses Ansatzes, “amorphe” Objekte mit unterschiedlichen Eigenschaften im Modell zu haben.

Natürlich erfordert dies eine gewisse Typsicherheit, zum Beispiel eine property int type für jedes Objekt in einem solchen Modell, und basierend darauf können Sie die verfügbaren Eigenschaften für das Objekt bestimmen. Mein üblicher Ansatz ist, a zu haben Loader für einen Delegaten, und lassen Sie ihn das Objekt als Datenquelle an verschiedene QML-UI-Implementierungen übergeben, die diesen Objekttyp visualisieren, den er instanziiert. Auf diese Weise haben Sie sowohl unterschiedliche Objekte im Modell als auch unterschiedliche QML-Elemente als Ansichtsdelegierte.

Der letzte Schritt zur Erstellung des ultimativen „Alleskönner“-Listen-/Modellobjekts ist die Implementierung QQmlListProperty und Q_CLASSINFO("DefaultProperty", "container") dafür, sodass Sie die Liste/das Modell dynamisch zusammenstellen oder die deklarative Syntax von QML verwenden können. Beachten Sie auch, dass Sie mit dieser Lösung einem solchen Modell etwas hinzufügen oder daraus entfernen und sogar deklarativ instanziierte Objekte entfernen können.

Abhängig von Ihrem Nutzungsszenario müssen Sie möglicherweise auch beides qmlRegisterType() oder qmlRegisterUncreatableType() für das Modell.

OK, auf den zweiten Blick sieht es so aus, als hätten Sie mit “Modell beliebiger Daten” nicht schemalose Modelle gemeint, sondern einfach unterschiedliche Schemamodelle. In diesem Fall, anstatt eine zurückzugeben AnimalModel *können Sie eine verwenden QAbstractListModel * oder sogar ein QObject * – es funktioniert sowieso in QML, da es Dynamik durch das Metasystem verwendet. Aber auf jeden Fall sind schemalose Modelle viel leistungsfähiger und flexibler, und sie brauchen keinen C++-Code, um definiert zu werden, es kann alles allein mit QML funktionieren.

class List : public QAbstractListModel {
    Q_OBJECT
    QList<QObject *> _data;

    Q_PROPERTY(int size READ size NOTIFY sizeChanged)
    Q_PROPERTY(QQmlListProperty<QObject> content READ content)
    Q_PROPERTY(QObject * parent READ parent WRITE setParent)
    Q_CLASSINFO("DefaultProperty", "content")
public:
    List(QObject *parent = 0) : QAbstractListModel(parent) { }
    int rowCount(const QModelIndex &p) const { Q_UNUSED(p) return _data.size(); }
    QVariant data(const QModelIndex &index, int role) const {
        Q_UNUSED(role)
        return QVariant::fromValue(_data[index.row()]);
    }
    QHash<int, QByteArray> roleNames() const {
        static QHash<int, QByteArray> roles = { { Qt::UserRole + 1, "object" } };
        return roles;
    }
    int size() const { return _data.size(); }
    QQmlListProperty<QObject> content() { return QQmlListProperty<QObject>(this, _data); }

public slots:
    void add(QObject * o) { insert(o, _data.size()); }

    void insert(QObject * o, int i) {
        if (i < 0 || i > _data.size()) i = _data.size();
        beginInsertRows(QModelIndex(), i, i);
        _data.insert(i, o);
        o->setParent(this);
        sizeChanged();
        endInsertRows();
    }

    QObject * take(int i) {
        if ((i > -1) && (i < _data.size())) {
            beginRemoveRows(QModelIndex(), i, i);
            QObject * o = _data.takeAt(i);
            o->setParent(0);
            sizeChanged();
            endRemoveRows();
            return o;
        } else qDebug() << "ERROR: take() failed - object out of bounds!";
        return 0;
    }

    QObject * get(int i) {
        if ((i > -1) && (i < _data.size())) return _data[i];
        else  qDebug() << "ERROR: get() failed - object out of bounds!";
        return 0;
    }

    void internalChange(QObject * o) { // added to force sort/filter reevaluation
      int i = _data.indexOf(o);
      if (i == -1) {
        qDebug() << "internal change failed, obj not found";
        return;
      } else {
        dataChanged(index(i), index(i));
      }
    }

signals:
    void sizeChanged();
};

Dann nach dir qmlRegisterType<List>("Core", 1, 0, "List"); Sie können es so ziemlich beliebig verwenden – es hält alles QObject oder abgeleitet, natürlich einschließlich QMLs QtObject Es kann direkt als Modell verwendet werden, um einen zu fahren ListView. Sie können es dynamisch mit den Slots oder deklarativ wie folgt füllen:

List {
    QtObject { ... }
    QtObject { ... }
    List {
        QtObject { ... }
        QtObject { ... }
    }
}

Es wird auch den Besitz von Objekten handhaben, und Sie können es leicht verschachteln und im Wesentlichen ein unterteiltes Baummodell erstellen – beachten Sie, dass Sie dies nicht deklarativ mit QMLs tun können ListModel. Vielleicht möchten Sie eine hinzufügen parentChanged signalisieren und einen Setter implementieren, der es aussendet, wenn man gegen einen wechselnden Elternteil binden will, das war in meinem Fall nicht nötig.

Um es mit einer Ansicht zu verwenden, können Sie entweder die objectName Eigentum oder ein int type Eigenschaft oder im Grunde jedes Mittel, um zwischen verschiedenen Objekttypen zu unterscheiden, und verwenden Sie a Loader für den Delegierten:

Loader {
    // using component in order to capture context props and present to the variable delegate
    sourceComponent: Qt.createComponent(obj.objectName + ".qml")
    // if that is not needed simply use
    // source: obj.objectName + ".qml"
    // or setSource to pass specific properties to delegate properties
    // Component.onCompleted: setSource(obj.objectName + ".qml", {/*prop list*/})
}

Aktualisieren:

Hier ist auch das Wesentliche der Implementierung für einen einfachen und ebenso dynamischen und generischen Sortier- und Filter-Proxy, der mit diesem Modell für eine verbesserte Benutzerfreundlichkeit einhergeht.

  • Vielen Dank für die sehr komplexe Antwort mit vielen interessanten Ideen. Sie hatten Recht, dass das Wurzelproblem ungültig war Q_PROPERTY Art. Mit AnimalModel* es funktioniert super. Zum Modell von QObject*, das habe ich bereits (stackoverflow.com/questions/35065627/…). Aber können Sie bitte ein Beispiel posten oder genauer sein Q_CLASSINFO und qmlRegisterType Anwendungsfall? Es scheint interessant, aber ich bin mir nicht sicher, wie genau ich es in diesem Fall verwenden soll.

    – Ludek Vodicka

    3. Februar 2016 um 7:07 Uhr

  • @LudekVodicka – Ich habe etwas Code hinzugefügt und mir erlaubt, Ihre Frage neu zu benennen, damit der Code für Leute zugänglicher ist, die nach einer ähnlichen Lösung suchen.

    – dtech

    3. Februar 2016 um 10:53 Uhr

  • Das ist ein absolutes Meisterwerk. Ich lerne Qml in den letzten anderthalb Monaten und hatte keine Ahnung, dass es auf so großartige Weise kombiniert werden kann. Vielen Dank für Erklärungen und Beispiele. Die einzige Frage, die mir in den Sinn kommt. Warum QHashMap als statischer ptr statt nur statisch? Es ist möglich, es als zu definieren static QHash<int, QByteArray> roles = { { ObjectRole, "object" } }; aber es braucht c++11. War das der Grund?

    – Ludek Vodicka

    3. Februar 2016 um 11:45 Uhr

  • Ja, es dient der Abwärtskompatibilität. Und ja, dieser Ansatz ist sehr flexibel und leistungsfähig, Sie können sehr komplexe Projekte nur mit diesem als C++-Code erstellen und alles andere in QML erledigen – er ist dynamisch, kann spontan generierten Code verwenden und ist viel schneller zu entwickeln in. Ich frage mich wirklich, warum etwas so Einfaches und doch so Mächtiges nicht von Anfang an in QML integriert wurde.

    – dtech

    3. Februar 2016 um 11:56 Uhr


  • Perfekt. Das ist genau der Grund, warum ich versuche, selbst zu entwickeln. Es ist etwas, von dem ich erwartet habe, dass Qml es von Anfang an haben wird. Es scheint, dass der aktuelle Qt/Qml-Status nur für die einfache UI-Entwicklung vorbereitet ist, aber nicht komplizierter. Vielen Dank für alle Antworten.

    – Ludek Vodicka

    3. Februar 2016 um 16:33 Uhr

986010cookie-checkWie erstellt man ein generisches Objektmodell zur Verwendung in QML?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy