How to update QAbstractItemModel view when a Data is updated



  • I use the Qt example for QAbstractItemModel and I try to update an Item to a given index.

    I tried to use emit DataChangedbut it doesn't work, the view is not updated.

    Here is an example:

    What I want: When you click on the button, it will update Data at index 0, the type of the animal will be changed, it will become a Lion.

    #include <QAbstractListModel>
    #include <QStringList>
    #include <qqmlcontext.h>
    //![0]
    class Animal
    {
    public:
        Animal(const QString &type, const QString &size);
    //![0]
    
        QString type() const;
        QString size() const;
    
        void setType(QString q) {
            m_type = q;
        }
    
    private:
        QString m_type;
        QString m_size;
    //![1]
    };
    
    class AnimalModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
    
        Q_INVOKABLE void test() ;
        void setName(const QString &name);
        enum AnimalRoles {
            TypeRole = Qt::UserRole + 1,
            SizeRole
        };
    
        AnimalModel(QObject *parent = 0);
    //![1]
    //!
    //!
        void setContext(QQmlContext *ctx) {
            m_ctx = ctx;
        }
    
        void addAnimal(const Animal &animal);
    
        int rowCount(const QModelIndex & parent = QModelIndex()) const;
    
        QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
        QHash<int, QByteArray> roleNames() const;
    
    protected:
    
    private:
        QList<Animal> m_animals;
        QQmlContext*  m_ctx;
    
    signals:
        void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
    //![2]
    };
    //![2]
    

    model.h

    #include "model.h"
    #include "qDebug"
    Animal::Animal(const QString &type, const QString &size)
        : m_type(type), m_size(size)
    {
    }
    
    QString Animal::type() const
    {
        return m_type;
    }
    
    QString Animal::size() const
    {
        return m_size;
    }
    
    
    
    
    AnimalModel::AnimalModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    void AnimalModel::addAnimal(const Animal &animal)
    {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_animals << animal;
        endInsertRows();
    }
    
    void AnimalModel::test() {
    
      m_animals[0].setType("Lion");
      emit dataChanged(QModelIndex(),QModelIndex());
      //I also tried:
      QModelIndex topLeft = createIndex(0,0);
      emit dataChanged(topLeft, topLeft);
    
    
    
     }
    
    int AnimalModel::rowCount(const QModelIndex & parent) const {
        Q_UNUSED(parent);
        return m_animals.count();
    }
    
    QVariant AnimalModel::data(const QModelIndex & index, int role) const {
        if (index.row() < 0 || index.row() >= m_animals.count())
            return QVariant();
    
        const Animal &animal = m_animals[index.row()];
        if (role == TypeRole)
            return animal.type();
        else if (role == SizeRole)
            return animal.size();
        return QVariant();
    }
    
    //![0]
    QHash<int, QByteArray> AnimalModel::roleNames() const {
        QHash<int, QByteArray> roles;
        roles[TypeRole] = "type";
        roles[SizeRole] = "size";
        return roles;
    }
    //![0]
    

    model.cpp

    #include "model.h"
    
    #include <QGuiApplication>
    #include <qqmlengine.h>
    #include <qqmlcontext.h>
    #include <qqml.h>
    #include <QtQuick/qquickitem.h>
    #include <QtQuick/qquickview.h>
    
    //![0]
    int main(int argc, char ** argv)
    {
        QGuiApplication app(argc, argv);
    
        AnimalModel model;
        model.addAnimal(Animal("Wolf", "Medium"));
        model.addAnimal(Animal("Polar bear", "Large"));
        model.addAnimal(Animal("Quoll", "Small"));
    
        QQuickView view;
        view.setResizeMode(QQuickView::SizeRootObjectToView);
        QQmlContext *ctxt = view.rootContext();
        ctxt->setContextProperty("myModel", &model);
    //![0]
    
        view.setSource(QUrl("qrc:view.qml"));
    
    
        view.show();
    
        return app.exec();
    }
    

    main.cpp

    import QtQuick 2.0
    import QtQuick 2.4
    import QtQuick.Controls 1.3
    import QtQuick.Window 2.2
    import QtQuick.Dialogs 1.2
    import QtQuick.Layouts 1.2
    import QtQml.Models 2.1
    import QtQuick.Controls.Styles 1.2
    
    
    //![0]
    ListView {
        width: 200; height: 250
    
        model: myModel
        delegate: Text { text: "Animal: " + type + ", " + size }
    
        MouseArea {
            anchors.fill: parent
            cursorShape: Qt.PointingHandCursor
            onClicked: {
    
            }
        }
        Button {
            anchors.bottom: parent.bottom
            width:50; height:50
            text:"click"
            onClicked: {
                myModel.test()
            }
    
        }
    
    }
    //![0]
    

    View.qml

    Do you have any idea why it doesn't work ?
    Thanks a lot !



  • The easiest solution:

    void AnimalModel::test() {
       layoutAboutToBeChanged();
       m_animals[0].setType("Lion");
       layoutChanged();
    }
    

  • Lifetime Qt Champion

    Hi,

    Just a quick thought, try adding the role parameter to your dataChanged call and put the type role in it.



  • Also, don't declare your dataChanged signal - emit the 'built-in' AbstractItemModel::dataChanged signal! As SGaist already mentioned, this should do the trick:

    emit dataChanged(topLeft, topLeft, { TypeRole }); 
    


    • remove void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight); from model.h
    • in AnimalModel::rowCount replace Q_UNUSED(parent); with if(parent.isValid()) return 0;
    • in AnimalModel::data add if(!index.isValid()) return QVariant(); Q_ASSERT(index.model()==this); before anything else
    • AnimalModel::test should be:
    m_animals[0].setType("Lion");
    const QModelIndex idx = index(0);
    emit dataChanged(idx ,idx );
    
    • createIndex should only be called inside the index method
    • for efficiency you might want to store the role names as a private member const QHash<int, QByteArray> roles = {std::pair<int,QByteArray>(TypeRole,"type"),std::pair<int,QByteArray>(SizeRole,"size")}; and AnimalModel::roleNames becomes just QHash<int, QByteArray> AnimalModel::roleNames() const {return roles;}

    For cases like this one, implementing a custom model is more a waste of time than anything else, QStandardItemModel could have done the trick effortlessly


    @SGaist said in How to update QAbstractItemModel view when a Data is updated:

    try adding the role parameter to your dataChanged call and put the type role in it.

    Unfortunately that parameter has been forgotten in the darkness. even built in Qt models do not use it



  • @Ray-Gray Thanks a lot, I will apply this to my code. I based my project on the QT example, and they didn't do that, I don't know why.

    Now it works fine, thanks all ! :)


Log in to reply
 

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