Set GridView model property from C++ without setti

2019-09-10 04:06发布

I'm trying to set the model property of a QML GridView from C++ by calling
QQmlProperty::write(gridview, "model", QVariant::fromValue(objlist));.

gridview is set correctly, I can modify the property from C++, when I set it to a QList with 6 entries and print them from QML I get
qml: model = item(0x30617b50), Item(0x30617b90), Item(0x30617bd0), Item(0x30617c10), Item(0x30617c50), Item(0x30617cd0), though the model is not being displayed.

The Qt documentation suggest calling

QQmlContext *ctxt = view->rootContext(); ctxt->setContextProperty("gridModel", QVariant::fromValue(objlist));

And then setting the property from QML with model: gridModel but that does not really suit my needs. It works fine though, as soon as the property is set the correct data is being displayed. When I print the variable from QML the output is
qml: model = [object Object] so there is definitely a difference between setting the context property and setting the object property, but I don't know how to fix this.

3条回答
劫难
2楼-- · 2019-09-10 04:45

If you say QQmlProperty::write correctly sets the model so what is your question? Anyway I suggest one more solution:

Let's say you have GridView like below:

GridView {
    anchors.fill: parent
    objectName: "grid"
    delegate: Rectangle {
        id: rect
        width: 100;
        height: 100
        color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
        Text {
            anchors.centerIn: rect
            text: modelData
        }
    }
}

objectName is mandatory.

So accessing from C++ could be:

QStringList list;
list.append("String1");
list.append("String2");
list.append("String3");

QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

QObject *obj = engine.rootObjects()[0]->findChild<QObject *>("grid");
if(obj) {
    obj->setProperty("model",QVariant(list));
}
查看更多
萌系小妹纸
3楼-- · 2019-09-10 04:48

The solution I came up with is slightly different from the answers posted, so I figured writing a clear answer would be best.

The key was to subclass QAbstractItemModel (or more precisely ..ListModel so you don't have to deal with rows/columns in C++ and QML).

When doing it this way, not only can you simply set the model as a property with

QQuickItem *mainform = view->rootObject();
QQuickItem *grid = (QQuickItem *)mainform->findChild<QObject*>("GridView object name");

ItemModel itemmodel;
itemmodel.setItems(objlist):

QQmlProperty::write(grid, "model", QVariant::fromValue(&itemmodel));

It also notifys QML whenever a change is made to the model, e.g. an Item deleted. (You'll have to handle the changes properly though, see removeRows() in the example)

Here is my ItemModel:

// itemmodel.h

#include <QAbstractListModel>
#include <item.h>

class ItemModel : public QAbstractListModel
{
    Q_OBJECT
public:
    explicit ItemModel(QObject *parent = 0);
    QHash<int, QByteArray> roleNames() const;

public slots:
    void setItems(QList<Item *> items);
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    int rowCount(const QModelIndex & parent = QModelIndex()) const;
    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());

private:
    QList<Item *> items;
};

// itemmodel.cpp
#include "itemmodel.h"

ItemModel::ItemModel(QObject *parent) : QAbstractListModel(parent)
{

}

// Column Names have to match all the Q_PROPERTYs defined in Item
const char* COLUMN_NAMES[] = {
    "property1",
    "property2",
    "...",
    NULL
};
QHash<int, QByteArray> makeRoleNames()
{
    int idx = 0;
    QHash<int, QByteArray> roleNames;
    while(COLUMN_NAMES[idx])
        roleNames[Qt::UserRole + idx + 1] = COLUMN_NAMES[idx++];

    return roleNames;
}

QHash<int, QByteArray> ItemModel::roleNames() const
{
    static const QHash<int, QByteArray> roleNames = makeRoleNames();
    return roleNames;
}

void ItemModel::setItems(QList<Item *> items)
{
    this->items = items;
}

int ItemModel::rowCount(const QModelIndex & /* parent */) const
{
    return items.count();
}

bool ItemModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(parent);
    beginRemoveRows(QModelIndex(), row, row + count - 1);
    while (count--) delete items.takeAt(row);
    // example for custom deletion:
    //              items.takeAt(row)->removeFromRoot();
    endRemoveRows();
    return true;
}

QVariant ItemModel::data(const QModelIndex &index, int role) const {
    if (!index.isValid())
        return QVariant();

    if (index.row() >= items.size() || index.row() < 0)
        return QVariant();

    if (role == Qt::DisplayRole) {
        return QVariant::fromValue(this->items.at(index.row()));
    }

    if (role > Qt::UserRole)
        return this->items.at(index.row())->property(makeRoleNames()[role]);
}

Sources:

  • [1] Qt Documentation Page about creating QAbstractItemModel subclasses for QML
  • [2] QAbstractItemModel reference
  • [3] QAbstractItemModel subclassing guide
  • [4] Forum post with a QAbstractListModel subclass
  • [5] Working (almost, data() slightly altered) implementation of QAbstractItemModel from which I took the rolenames functions
查看更多
闹够了就滚
4楼-- · 2019-09-10 04:56

Instead of attempting to access QML objects or properties from C++ I would suggest to using bindings on the QML side and provide the property values from C++.

If exposing the model instance via setContextProperty doesn't quite fit your needs, e.g. if the model is instantiated after QML loading time, then I would suggest the following approach:

  1. expose an instance of a QObject derived class via setContextProperty(), i
  2. that class gets a Q_PROPERTY for your model, including a NOTIFY signal
  3. in QML you bind that property to the GridView's model
  4. whenever you have create the model in C++ or when you need to create a new instance of the model, you emit the above mentioned NOTIFY signal

The interface class would look somewhat like this

class MyInterface : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MyModel* model READ model NOTIFY modelChanged)

public:
    MyModel *model() const { return m_model; }

    void setModel(MyModel *model) {
        m_model = model;
        emit modelChanged();
    }

private:
    MyModel *m_model = 0;
};

Of course instead of the setter the change of m_model could be internal to MyInterface, etc. That gives you full control on the C++ side on when to create a model instance, when to change it, when to delete it. If you change the type to QAbstractItemModel*, or some common model base class of yours, you could even change the type of model during runtime as you see fit

查看更多
登录 后发表回答