Change data on QAbstractItemModel subclass model



  • Hello, I have written such classes to get c++ model data from qml. How can I change specific QList item (for example Description) after I write all the data to the QList?

    Headers:

    #include <QAbstractListModel>
    #include <QStringList>
    
    class Item
    {
    public:
        Item(QString name, QString description, QString price, QString code, int id);
    
        QString getName() const;
        QString getDescription() const;
        QString getPrice() const;
        QString getCode() const;
        int getId() const;
    
    private:
        QString name;
        QString description;
        QString price;
        QString code;
        int id;
    };
    
    class ItemModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        enum ItemRoles {
            NameRole,
            DescriptionRole,
            PriceRole,
            CodeRole,
            IdRole
        };
    
        ItemModel(QObject *parent = 0);
    
        void addItem(const Item &item);
    
        int rowCount(const QModelIndex & parent = QModelIndex()) const;
    
        QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
    
        QList<Item> item() const;
    
        void clearList();
    
        void setItem(const QList<Item> &item);
    
    protected:
        QHash<int, QByteArray> roleNames() const;
    private:
        QList<Item> m_item;
    };
    

    .cpp

    #include "item.h"
    
    Item::Item(QString name, QString description, QString price, QString code, int id)
    {
        this->name = name;
        this->description = description;
        this->price = price;
        this->code = code;
        this->id = id;
    }
    
    QString Item::getName() const
    {
    return name;
    }
    
    QString Item::getDescription() const
    {
    return description;
    }
    
    QString Item::getPrice() const
    {
    return price;
    }
    
    QString Item::getCode() const
    {
    return code;
    }
    
    int Item::getId() const
    {
    return id;
    }
    
    ItemModel::ItemModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    void ItemModel::addItem(const Item &item)
    {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_item << item;
        endInsertRows();
    }
    
    int ItemModel::rowCount(const QModelIndex & parent) const {
        Q_UNUSED(parent);
        return m_item.count();
    }
    
    QVariant ItemModel::data(const QModelIndex & index, int role) const {
        if (index.row() < 0 || index.row() >= m_item.count())
            return QVariant();
    
        const Item &item = m_item[index.row()];
        if (role == NameRole)
            return item.getName();
        else if (role == DescriptionRole)
            return item.getDescription();
        else if (role == PriceRole)
            return item.getPrice();
        else if (role == CodeRole)
            return item.getCode();
        else if (role == IdRole)
            return item.getId();
        return QVariant();
    }
    
    QHash<int, QByteArray> ItemModel::roleNames() const {
        QHash<int, QByteArray> roles;
        roles[NameRole] = "name";
        roles[DescriptionRole] = "description";
        roles[PriceRole] = "price";
        roles[CodeRole] = "code";
        roles[IdRole] = "id";
        return roles;
    }
    
    void ItemModel::setItem(const QList<Item> &item)
    {
        m_item = item;
    }
    
    QList<Item> ItemModel::item() const
    {
        return m_item;
    }
    
    void ItemModel::clearList()
    {
        m_item.clear();
    }
    
    


  • @m.kuncevicius roleNames() should be public. For mutable items you either have to write your own interface, i.e. functions which change the internal data and use the standard signals to tell listeners when the data is changed, or reimplement setData. The latter is obligatory if the data should be editable through a view UI.

    A hint for coding style: always use the 'override' C++11 keyword when appropriate (http://en.cppreference.com/w/cpp/language/override), it makes the code less error-prone and easier to read.



  • I want to update data from c++ side. I tried to write setters for Item class, but when I try to use them an error apiers: passing 'const Item' as 'this' argument discards qualifiers [-fpermissive]
    m_item.at(i).setDescription(value);
    How to change the internal data the right way?



  • @m.kuncevicius Maybe you tried to do something like

    void Item::setDescription(QString desc) const
    

    In that case you have to remove 'const' because it's a setter and quite logically modifies the 'this' object and therefore can't be a const member function. Using the keyword const like this is meant for the member functions which don't modify the perceived state of the 'this' object. (See e.g. https://isocpp.org/wiki/faq/const-correctness#const-member-fns .)



  • @Eeli-K Thank you for answer, but setter is not const.



  • @m.kuncevicius Then you have to give the code, otherwise it's impossible to say where the problem is. The error message refers to a situation where a method is const but the this object is changed in it.



  • @Eeli-K I just added auto genereted setter. The rest of my code:

    Header:

        void setName(const QString &value);
    

    .cpp

    void Item::setName(const QString &value)
    {
        name = value;
    }
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "item.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
        auto context = engine.rootContext();
        engine.load(QUrl(QLatin1String("qrc:/main.qml")));
    
        ItemModel itemModel;
    
        itemModel.addItem(Item("Name1", "Description1", "12,25", "113548", 1));
        itemModel.addItem(Item("Name2", "Description2", "22,25", "213548", 2));
        itemModel.addItem(Item("Name3", "Description3", "32,25", "313548", 3));
    
        itemModel.item().at(1).setName("Changed");
    
        context->setContextProperty("itemModel", &itemModel);
    
        return app.exec();
    }
    


  • @m.kuncevicius
    Please paste the error message here and tell what code line it refers to.

    QList<Item> ItemModel::item() const
    {
        return m_item;
    }
    

    returns a copy so you can't change the model's item through that. But even if it would return a reference or a pointer the model wouldn't work properly because it wouldn't send signals when items are changed. Write the modifier API in the model class, hide the item completely inside it. Either write something like ItemModel::setName(int index, QString name) or override setData. In the model's code when the model data is changed you have to emit some signals; see the QAbstractItemModel Subclassing documentation.



  • @Eeli-K said in Change data on QAbstractItemModel subclass model:

    ItemModel::setName(int index, QString name)

    I tried this before. But it shows the same error:
    item.cpp:94: error: passing 'const Item' as 'this' argument discards qualifiers [-fpermissive]
    m_item.at(index).setName(name);
    ^

    void ItemModel::setName(int index, QString name)
    {
        m_item.at(index).setName(name);
    }
    


  • @m.kuncevicius Maybe it's because
    const T &QList::at(int i) const
    returns a const. Try the [] operator.

    Then add emit dataChanged to that data modifying method so that the view is automatically updated.



  • on top of using operator[],

    int ItemModel::rowCount(const QModelIndex & parent) const {
        Q_UNUSED(parent);
        return m_item.count();
    }
    

    is infinite recursion. for a list it should be:

    int ItemModel::rowCount(const QModelIndex & parent) const {
        if(parent.isValid()) return 0;
        return m_item.count();
    }
    

    void setItem(const QList<Item> &item); why do you replace the entire list every time? You'll be forced to emit modelReset() every time and it's not great.

    if (index.row() < 0 || index.row() >= m_item.count()) what if the index is invalid? what if the the index has a parent? what happens if I pass an index created by another model?

    Why don't you just use a QStandardItemModel instead of building your own?



  • @Eeli-K Thank you for answers and suggestions!
    It works right now:

    void ItemModel::setName(int index, QString name)
    {
        m_item[index].setName(name);
        QModelIndex topLeft = createIndex(index,0);
        emit dataChanged(topLeft, topLeft);
    }
    

    @VRonin Thanks for comments on my code. Maybe you know some examples how to use QStandardItemModel because reading documentation is pretty exhausting.



  • just use the QAbstactItemModel interface. call insertRows(), insertColumns(), setData() and data(). basically you don't have to engineer the ItemModel or the Item at all, no need for subclassing anything, just use it


Log in to reply