0

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.

yspreen
  • 1,759
  • 2
  • 20
  • 44
  • How to set the model property with `QQmlProperty::write`, instead of `ctxt->setContextProperty`, or generally speaking how to set the model of a GridView without using global context variables – yspreen Sep 20 '16 at 12:15
  • Instead of _setting_ the model from C++, isn't it an option to define an empty model in GridView and, in C++, _update_ the model using functions of QAbstractItemModel? This will trigger the corresponding signals allowing QML to update the GridView. – Marc Van Daele Sep 20 '16 at 12:53
  • That _does_ sound a lot better, especially since I'm running into problems now when trying to remove Items from my model.. How exactly would I go about doing that then? I find it very hard to find sufficient documentation on C++ / QML integration.. – yspreen Sep 22 '16 at 14:02
  • First you should in C++ retrieve the model property from QML (eg using http://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html#properties). Then I would suggest to print out the type/class of the model but most likely it will be a (subclass of) QAbstractItemModel. You also might want to have a look at QQmlObjectListModel in http://gitlab.unique-conception.org/qt-libraries/lib-qt-qml-tricks – Marc Van Daele Sep 23 '16 at 06:20
  • I now went with the approach to subclass QAbstractItemModel, I'm _just_ in the process of writing an answer, will link when that's done. – yspreen Sep 23 '16 at 06:21
  • [Here](http://stackoverflow.com/a/39654426/2585092) it is. – yspreen Sep 23 '16 at 06:57

3 Answers3

1

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

Kevin Krammer
  • 5,159
  • 2
  • 9
  • 22
  • That seems to me like a valid approach. Since the previous implementation with the context did _kind of_ work, I went ahead and continued crafting my application with it, but now I ran into a problem when deleting an element from the view. It leaves a gap when setting it invisible and from QML I cant seem to access any remove method for the model, so would your implementation allow QML to delete an item from the model? If it does, could you give any sources on how to do that? – yspreen Sep 22 '16 at 14:08
  • With your suggestions I came up with [this](http://stackoverflow.com/a/39654426/2585092) solution – yspreen Sep 23 '16 at 06:57
  • I am not sure what you mean with "would your implementation allow QML to delete an item from the model?" Deleting an item from the model is orthogonal/independent on how you access the model. The model simply needs to use the appropriate beginRemoveRows() and endRemoveRows() calls to notify the view about the row removal – Kevin Krammer Sep 23 '16 at 18:50
0

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));
}
folibis
  • 12,048
  • 6
  • 54
  • 97
  • I need the list to be a list of Objects since I have more than one property per GridView element to be set. And for the model property being set correctly, it seems like it should be correct when it prints a list of Items, but it does not display anything. GridView seems to expect model to be an `[object Object]` to be able to display it, but it's set as `item(0x30617b50), ...` – yspreen Sep 20 '16 at 12:53
  • That isn't problem to rewrite the example to use QObject list. And what does it mean - " it prints a list of Items"? What do that? Can you post the code? – folibis Sep 20 '16 at 12:57
  • It looks that you didn't expose your object type to QML. You should use either `qmlRegisterType` or `qmlRegisterUncreatableType` – folibis Sep 20 '16 at 13:04
  • `It looks that you didn't expose your object type to QML` I'm not sure what to do with that information... – yspreen Sep 20 '16 at 13:04
  • QML knows nothing about your object type. So it relates to it as QObject. Expose the type with either `qmlRegisterType` or `qmlRegisterUncreatableType` to QML – folibis Sep 20 '16 at 13:07
  • Do you think that would make the line that says object say item(), ... Or the other way round? – yspreen Sep 20 '16 at 13:10
  • I think you got the impression that the result `model = [object Object]` is the problem, but actually it's that when setting the model property the result is `model = Item(0x2f16def8),...,Item(0x2f16e078)` – yspreen Sep 21 '16 at 14:02
0

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
yspreen
  • 1,759
  • 2
  • 20
  • 44
  • I would advise against setting a QML objects propery from C++, that makes your C++ code depend on a specific QML composition. The rest seems to deal with the implementation of a list model, which is orthogonal to getting the model used by QML and not really relevant to the question at hand – Kevin Krammer Sep 23 '16 at 18:53
  • It _is_ orthogonal to getting the modifying it because that's not what I wanted to do in the first place. I wanted to set the property from C++ as stated in my question. Modifying the existing property would have been a workaround but after researching further into the QAbstractItemModels I found that they are able to do what I wanted to do in the first place, hence I did not have to make use of the workaround that your answer is. – yspreen Sep 24 '16 at 10:22
  • Your question was how to set the model property, so the part of your solution dealing with the model implementation is answering a different question. Just wanted to point that out in case someone else read the question and was wondering how the model class played into it. The difference between the code in your question and your answer is to set the view's model via `QObject::setProperty` instead of setting it via a property binding. – Kevin Krammer Sep 24 '16 at 10:28
  • To quote my question: "I'm trying to set the model property of a QML GridView from C++ by calling QQmlProperty::write" which is exactly what I explain in my answer. It is possible to do this by subclassing QAbstractItemModel and in the answer I wrote out how I did that. I don't see how my answer doesn't fit the question. – yspreen Sep 24 '16 at 10:59
  • The model has nothing to do with the setting of the property. Using a QAbstractListModel is one option of provinding data for a GridView. Your question wasn't actually about setting the GridView's model, you already had that solved, your question was actually about modifying a model's content. Which is best done with a list model implementation. Your solution is currently a mix of the best option for modifyable data and the worst option for getting the model into the view. – Kevin Krammer Sep 24 '16 at 15:00
  • You got it wrong then, I was not able to set the model property. As stated in my question it was set, but not displayed, so the setting obviously didn't work. The only thing that did work was setting the context property and that was what I wanted to avoid – yspreen Sep 24 '16 at 15:07
  • If you don't want to set a context property at all, i.e. neither the list model nor an interface object, you can still register the model class as a QML type and instantiate it from QML. All of these options are better then writing to a QML object's property from C++ – Kevin Krammer Sep 24 '16 at 15:31