[Solved] Writing to one role of a C++ provided ListModel



  • Hello,

    I've used the ListModel example by Christophe Dumez here:
    http://cdumez.blogspot.com/2010/11/how-to-use-c-list-model-in-qml.html

    As the basis for subclassing a QAbstractListModel for access in QML.

    All of the examples I find for a QML ListView and C++ ListModel, illustrate accessing the model, which I have working. I'm also able to add a new row to the ListModel via the appendRow() method.

    The part that's not working is the ability to overwrite the value of one role within the last row of the model.

    I can't find any guidance on writing to an existing item within a C++ model from QML.

    I've written the setData() method for editable model, as described here:
    http://doc.qt.digia.com/main-snapshot/qabstractlistmodel.html

    It seems the data() and setData() methods are somehow used by the QML engine, but I don't understand how to access this setData() method from within my QML script.

    In addition to extending the ListModel class to include setData(), I created a DataItem class, derived from the ListItem class, and containing the specific roles for this application. I then created a DataModel class which derives from ListModel and makes certain methods Q_INVOLKABLE so they are accessable from QML.

    Can someone please help me understand how to overwrite the value of a specific role, in a specific item of an editable C++ listmodel, from QML?

    Any clues are very greatly apprieciated, I've been banging my head on this for days now.

    This is my ListModel::setData() to extend the original ListModel example:

    @bool ListModel::setData ( const QModelIndex & index, const QVariant & value, int role )
    {
    if(index.row() < 0 || index.row() >= m_list.size())
    return false;
    m_list.at(index.row())->data(role) = value;
    return true;
    }@

    This is the DataModel.h exposing Q_INVOLKABLE methods:
    @#ifndef QDATAMODEL_H
    #define QDATAMODEL_H

    // QDataModel derives from ListModel and knows about our specific DataItem

    #include <QtGui>
    #include "DataItem.h"

    class QDataModel : public ListModel
    {
    Q_OBJECT

    public:
    QDataModel(QObject* parent = 0);
    Q_INVOKABLE void append (const QString &timestamp, const QString &action, const QString &value, const QString &note);
    Q_INVOKABLE void addnote (const QModelIndex & index, const QString &note);
    };

    #endif // QDATAMODEL_H@

    This is the DataModel.cpp providing the Q_INVOLKABLE methods:
    @#ifndef QDATAMODEL_CPP
    #define QDATAMODEL_CPP

    #include "QDataModel.h"

    QDataModel::QDataModel(QObject* parent) : ListModel(new DataItem, parent)
    {
    }

    Q_INVOKABLE void QDataModel::append (const QString &timestamp, const QString &action, const QString &value, const QString &note)
    {
    appendRow(new DataItem(timestamp, action, value.toDouble(), note));
    }

    Q_INVOKABLE void QDataModel::addnote (const QModelIndex & index, const QString &note)
    {
    qDebug() << "setData: " << note;
    setData ( index, note, DataItem::NoteRole );
    }

    #endif // QDATAMODEL_CPP@

    This is DataItem.h deriving from ListItem with application specific roles:
    @#ifndef DATAITEM_H
    #define DATAITEM_H

    // DataItem derives from ListItem and specifies the Roles our model will provide

    #include "ListModel.h"

    class DataItem : public ListItem
    {
    Q_OBJECT

    public:
    enum Roles {
    TimestampRole = Qt::UserRole+1,
    ActionRole,
    ValueRole,
    NoteRole
    };

    public:
    DataItem(QObject *parent = 0): ListItem(parent) {}
    explicit DataItem(const QString &timestamp, const QString &action, const qreal value, const QString &note, QObject *parent = 0);
    QVariant data(int role) const;
    QHash<int, QByteArray> roleNames() const;
    void setValue(const qreal value);
    void setNote(const QString &note);
    inline QString id() const { return m_timestamp; }
    inline QString timestamp() const { return m_timestamp; }
    inline QString action() const { return m_action; }
    inline qreal value() const { return m_value; }
    inline QString note() const { return m_note; }

    private:
    QString m_timestamp;
    QString m_action;
    qreal m_value;
    QString m_note;
    };

    #endif // DATAITEM_H@

    and the DataItem.cpp with methods to read and write application specific roles:
    @#ifndef DATAITEM_CPP
    #define DATAITEM_CPP

    #include "DataItem.h"

    DataItem::DataItem(const QString &timestamp, const QString &action, const qreal value, const QString &note, QObject *parent) :
    ListItem(parent), m_timestamp(timestamp), m_action(action), m_value(value), m_note(note)
    {
    }

    void DataItem::setValue(const qreal value)
    {
    if(m_value != value) {
    m_value = value;
    emit dataChanged();
    }
    }

    void DataItem::setNote(const QString &note)
    {
    if (!m_note.isEmpty())
    {
    m_note.append(",");
    }
    m_note.append(note);
    emit dataChanged();
    }

    QHash<int, QByteArray> DataItem::roleNames() const
    {
    QHash<int, QByteArray> names;
    names[TimestampRole] = "timestamp";
    names[ActionRole] = "action";
    names[ValueRole] = "value";
    names[NoteRole] = "note";
    return names;
    }

    QVariant DataItem::data(int role) const
    {
    switch(role) {
    case TimestampRole:
    return timestamp();
    case ActionRole:
    return action();
    case ValueRole:
    return value();
    case NoteRole:
    return note();
    default:
    return QVariant();
    }
    }

    #endif // DATAITEM_CPP@

    Sorry to include so much code, I'm just trying to give the complete picture of how I extended the Dumez ListModel example.

    The DataModel::append() is working from QML and adds new rows to the model, but the DataModel::updatenote() is not updating the "note" role in the last item.

    It seems I may also need to extend ListItem to include a ListItem::setData(), I'm just not sure how all this bolts together.

    Thank You Very Much for Suggestions or Examples!!!

    johnea



  • OK,

    As is often the case, I answered my own question 8-) Sometimes it just takes writing it all down and then reading it again afterwards to start making the pieces come together.

    I did further extend Christophe's ListItem class with it's own pure virtual setData():
    @...
    virtual bool setData(const QVariant & value, int role) = 0;
    ...@

    I then implimented this in the derived, application specific, DataItem class:
    @bool DataItem::setData(const QVariant & value, int role)
    {
    switch(role) {
    case ValueRole:
    setValue(value.toReal());
    return true;
    case NoteRole:
    setNote(value.toString());
    return true;
    default:
    return false;
    }
    }@

    Then in the ListModel class, derived from QAbstractListModel, ListModel::setData() now calls ListItem::setData():
    @bool ListModel::setData ( const QModelIndex & index, const QVariant & value, int role )
    {
    if(index.row() < 0 || index.row() >= m_list.size())
    return false;
    return m_list.at(index.row())->setData(value, role);
    }@

    The method above maintains the "const QModelIndex & index" parameter, because this is the way it was prototyped in the subclassing guide. But since I don't know how to get a model index from QML, I also extended ListModel to fetch index given a row integer. Although it seems weird to convert from row to index and then back to row, here it is:
    @QModelIndex ListModel::indexFromRow( const int row ) const
    {
    return index(row);
    }@

    With all of this, the application specific QDataModel's QML invokable method addnote() became:
    @Q_INVOKABLE void QDataModel::addnote (const int row, const QString &note)
    {
    qDebug() << "setData: " << note << " on row " << row;
    setData ( indexFromRow(row), note, DataItem::NoteRole );
    }@

    This allowed this line in the QML script to set the note value in the chosen item in the model:
    @datamodel.addnote( resultindex, textdisplay.text );@

    In addition to the Q_INVOKABLE keyword in the declaration of QDataModel::addnote() above, these two lines are required in main.cpp to export the addnote() method to the QML engine:
    @QDataModel model(qApp);
    view.rootContext()->setContextProperty("datamodel", &model);@

    I hope this can help someone else trying to piece this puzzle together.

    Now the next step for us is to add persistent storage behind this C++ ListModel. Since this application only appends data (row in addnote() is always the last one in the model), we're going to use file I/O as the backing store, and avoid the complexity of SQL.

    Even though I was just talking to myself 8-) I hope this trail of bread crumbs can help someone else.

    Thanks to everyone who contributes to the Qt/QML community!

    johnea



  • Hi, there is something I don't understand here, I dont understand why there is no way to set the data of a DataItem more easily. Let me explain myself :

    In the delegate of the QML list view, we can easily refer to properties thanks to the property <-> role mapping. So, when we write the following :

    @
    ListView {
    ...
    // This is a C++ ListModel of DataItems
    model: datamodel
    ...
    delegate: Text {
    ...
    // note is the rolename assiociated to NoteRole enum.
    // The following line calls
    // ListModel::data(const QModelIndex &index, int role)
    // I then assume that index is known somewhere in QML
    text: note
    ...
    MouseArea {
    ...
    onDoubleClicked: {
    // I expected the following line to call
    // ListModel::setData() but it's not.
    // the application output tells me that I can't access
    // a read-only property, why ??
    // Event if DataItem had a
    // Q_PROPERTY (note READ note WRITE setNote ...)
    // I'd still have the message.
    parent.text = "I changed the note"
    }
    }
    }
    ...
    }
    @

    Why can't we set a property without worrying about the index ?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.