Qt accessing model data outside ItemDelegate

我是研究僧i 提交于 2019-12-08 06:08:54

问题


I have some model class that inherits QAbstractListModel:

VehiclesModel.h:

class VehiclesModel : public QAbstractListModel {
    Q_OBJECT

    public:
        enum Roles {
            ImagePathRole = Qt::UserRole + 1,   // QString
            NameRole                            // QString
        };

        virtual int rowCount(const QModelIndex & parent = QModelIndex()) const override { ... }
        virtual QVariant data(const QModelIndex & index, int role) const override { ... }
        virtual QHash<int, QByteArray> roleNames() const override {
            QHash<int, QByteArray> roles = QAbstractListModel::roleNames();

            roles[ImagePathRole] = "imagePath";
            roles[NameRole] = "name";

            return roles;
        }
};

main.cpp:

#include "VehiclesModel.h"

int main(int argc, char * argv[]) {
    QGuiApplication app(argc, argv);
    VehiclesModel vehiclesModel;
    QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("vehiclesModel", &vehiclesModel);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

And ComboBox that displays this model: main.qml:

ComboBox {
    id: control
    model: vehiclesModel
    delegate: ItemDelegate {
        contentItem: RowLayout {
            Image {
                source: imagePath
            }
            Label {
                text: name
            }
        }
        highlighted: control.highlightedIndex == index
    }
    contentItem: RowLayout {
        Image {
            source: ??imagePath??
        }
        Label {
            text: ??name??
        }
    }
}

I want to customize the ComboBox to show vehicle image and name. I can access to model data from ItemDelegate but how to access to model data outside the ItemDelegate? For example I want to access current index data (ImagePathRole and NameRole) to display vehicle image and name in contentItem.

Is it possible to do it without calling QAbstractListModel methods directly (i.e. index() and data() methods) and making them Q_INVOKABLE?


回答1:


Not in any sort of a decent built-in way at the present time, unfortunately, this is something I've found to be lacking for quite a while, and I've considered implementing something for this in the QML models functionality, but I haven't yet had the time to do so.

For the time being, you can either do it yourself (like you're discussing), at the cost of type-safety and so on, or (the way I've typically tackled this before), you can create a QObject subclass to represent a single item in the model (ItemDataThing or whatever you choose to call it); provide it with a source model & index, properties, and let it represent a single instance of data from the model.

Something like:

class ImageDataThing : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString imagePath READ imagePath NOTIFY imagePathChanged)
    Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
    Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)

public:
    QString imagePath() const;
    QAbstractItemModel *model() const;
    void setModel(const QAbstractItemModel *newModel);
    int index() const;
    void setIndex(int newIndex);
signals:
    void imagePathChanged(const QString &imagePath);
    void modelChanged(QAbstractItemModel *model);
    void indexChanged(int indexChanged);
};

... and in your implementation, whenever the model is set, hook the change signals (e.g. rowsInserted, rowsRemoved, ...) to alter the stored index (if provided) to keep it mapped to the correct place in the model.

In the model data getters (here, imagePath for instance), access the model instance (using the index) to grab the data out, and return it.

This has the obvious disadvantage of being a lot of boilerplate, but on the other hand, it's easy-to-write code if you are familiar with models, type-safe, and one could autogenerate it fairly easily.




回答2:


You could create your own function to get data from the model, like the one I'm currently using, VehiclesModel.h:

public slots:
    int size() const;   // to access from QML javascript
    QVariant getData(int index, int role);  // to access from QML javascript

VehiclesModel.cpp:

int VehiclesModel::size() const {
    return m_list.size();
}

QVariant VehiclesModel::getData(int index, int role) {
    if (index < 0 || index >= m_list.count())
        return QVariant();
    switch (role) {
    case ImagePathRole:
        return ...
        break;
    default:
        break;
    }
}



回答3:


I strongly suggest to look at the Qt QML Tricks library made by Thomas Boutroue:

http://gitlab.unique-conception.org/qt-qml-tricks/

More specific the QQmlObjectListModel (from the Qt QML Models) could do the trick for you.

Expanding with using the Qt Super-Macros, it reduces overhead writing setters/getters! These macros basically expand to a Q_PROPERTY, resulting in accessibility from QML, and add definition of a setter, getter and private variable.

Usage in your specific case this could look something like this, quickly written down, not validated (check using the correct index for referencing the model):

VehicleItem.h:

#include <QObject>
#include "QQmlVarPropertyHelpers.h" // Include library Qt Super-Macros

class VehicleItem : public QObject {
    Q_OBJECT

    QML_WRITABLE_VAR_PROPERTY(QString, imagePath)
    QML_WRITABLE_VAR_PROPERTY(QString, name)

public:
    explicit VehicleItem(QString imagePath, QString name, QObject* parent=0)
    : QObject   (parent)
    , m_imagePath (imagePath)
    , m_name      (name)
    {}

};

VehiclesModel.h:

#include <QObject>
#include "QQmlObjectListModel.h" // Include library Qt QML Models
#include "VehicleItem.h"

class VehiclesModel : public QObject {
    Q_OBJECT

    QML_OBJMODEL_PROPERTY(VehicleItem, modelList)

public:
    explicit VehiclesModel(QObject *parent = 0);

};

VehiclesModel.c:

#include "VehiclesModel.h"

VehiclesModel::VehiclesModel(QObject *parent) :
    QObject(parent), m_modelList(new QQmlObjectListModel<VehicleItem>())
{}

main.c (remains the same):

#include "VehiclesModel.h"

int main(int argc, char * argv[]) {
    QGuiApplication app(argc, argv);
    VehiclesModel vehiclesModel;
    QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("vehiclesModel", &vehiclesModel);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

main.qml:

ComboBox {
    id: control
    model: vehiclesModel
    delegate: ItemDelegate {
        contentItem: RowLayout {
            Image {
                source: imagePath
            }
            Label {
                text: name
            }
        }
        highlighted: control.highlightedIndex == index
    }
    contentItem: RowLayout {
        Image {
            source: vehiclesModel.modelList.get(index).imagePath
        }
        Label {
            text: vehiclesModel.modelList.get(index).name
        }
    }
}

As modelList (and also imagePath and name) is expanded by the macro to a Q_PROPERTY, it is accessible from QML side.

For the ins-and-outs of this library, be sure to check Thomas Boutroue's lightning talk at the QtWS2015: https://www.youtube.com/watch?v=96XAaH97XYo




回答4:


Shameless plug for my SortFilterProxyModel library.

The problem you are asking is actually a head-scratching scenario. I've found a way to do it somewhat correctly but it's kinda complicated and involves an external library. In my solution we filter the source model to only expose the element corresponding to the current index of the combo box and instantiate a delegate for this element and use it as the contentItem of the ComboBox.

This has the advantage of not having to modify your model and keeping in synch with your model changes.

import SortFilterProxyModel 0.2 // from https://github.com/oKcerG/SortFilterProxyModel
import QtQml 2.2

/*
...
*/

ComboBox {
    id: control
    model: vehiclesModel
    delegate: ItemDelegate {
        contentItem: RowLayout {
            Image {
                source: imagePath
            }
            Label {
                text: name
            }
        }
        highlighted: control.highlightedIndex == index
    }
    contentItem: { currentIndex; return selectedInstantiator.object; } // use currentIndex to force the binding reevaluation. When the model changes, the instantiator doesn't notify object has changed
    Instantiator {
        id: selectedInstantiator
        model: SortFilterProxyModel {
            sourceModel: control.model
            filters: IndexFilter {
                minimumIndex: control.currentIndex
                maximumIndex: control.currentIndex
            }
        }
        delegate: RowLayout {
            Image {
                source: imagePath
            }
            Label {
                text: name
            }
        }
    }
}


来源:https://stackoverflow.com/questions/41591544/qt-accessing-model-data-outside-itemdelegate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!