Dynamic Upadate of data to a ListModel



  • Hi

    I have a model very similar to this model.

    class DataList{
    public:
        DataList(const QString &name, const QString &value, const QString &category);
    
        QString name() const;
        QString value() const;
        QString category() const;
    
    private:
        QString m_name;
        QString m_value;
        QString m_category;
    };
    
    class DataModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        enum DataRoles {
            NameRole = Qt::UserRole + 1,
            ValueRole,
            CategoryRole
        };
    
        DataModel(QAbstractItemModel *parent = 0);
        virtual ~DataModel() {}
        void addData(const UAVDataList &uavdatalist);
        int rowCount(const QModelIndex & parent = QModelIndex()) const;
    
        QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
    protected:
        QHash<int, QByteArray> roleNames() const;
    
    private:
        QList<DataList> m_datalist;
    
    };
    

    Based on this, my model has 3 roles. The value variable in the DataList gets continuously updated and all value variables are Q_Property with well defined get, set and signals.

    I have attached this model in a listview. The listview shows only the initial values. How do I update the values when there is a change in the values?


  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    How do I update the values when there is a change in the values?

    you need to emit the dataChanged() signal from your model whenever the data for a given index changes.



  • Hi,
    ...or use the begin*, end* functions described in doc.qt.io/qt-5/qabstractitemmodel.html#protected-functions and e.g. used in this example http://doc.qt.io/qt-5/model-view-programming.html. This will probably result in calling the dataChanged() signal indirectly :-)
    -Michael.



  • Thanks!!

    I will try and update if it works



  • @m.sue

    I am not adding a new row. I am only modifying a value so I guess datachanged() is a better choice.


  • Moderators

    @m.sue said in Dynamic Upadate of data to a ListModel:

    This will probably result in calling the dataChanged() signal indirectly :-)

    actually no.
    The dataChanged() signal is for the case (surprise) when the data (of an existing item) has changed .
    The methods mentioned by you should be self-explanatory. So there is no dataChanged() signal necessary.



  • @saitej: you had an addDatamethod, so I suspect you would really add some data.
    @raven-worx: Ah, I see, then they probably emit the \*AboutToBe\* signals, as somehow, of course, the widget must know about these actions.
    -Michael.



  • @m.sue

    Ya for adddata method I am already using the beginInsertRows :)



  • @raven-worx

    Hi

    I didn't get you. If I need to change the existing data, how do i do it with out datachanged() signal


  • Moderators

    @m.sue said in Dynamic Upadate of data to a ListModel:

    Ah, I see, then they probably emit the *AboutToBe* signals, as somehow, of course, the widget must know about these actions.

    yes, the QAbstractItemView of course does connect to those signals. If you have a custom object/widget, you would also need to connect to those signals.

    @saitej said in Dynamic Upadate of data to a ListModel:

    I didn't get you. If I need to change the existing data, how do i do it with out datachanged() signal

    i said: you only need in the case existing data has changed



  • @raven-worx

    void QAbstractItemModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int> ())
    

    Is this used to update the entire model or Can it be used to update a specific item in the listview? If It can be used to update a specific item then what are the arguments (instead of bootrom right)?

    Thanks!!


  • Moderators

    @saitej
    if it's just a single cell, then topLeft and bottomRight are the same index.
    If you want to update the whole row, then topLeft is QModelIndex(row,0) and bottomRight is QModelIndex(row,colCount-1).
    Also it is possible to set topLeft to QModelIndex(0,0) and bottomRight to QModelIndex(rowCount-1,colCount-1) to update the whole table/list.

    Note that ranges are only supported when topLeft and bottomRight have the same parent index. So as long as you don't use your model in a treeview your are fine.



  • @raven-worx

    Thanks a lot!!



  • @raven-worx

    Hi

    I am trying to connect a property change signal to datachanged signal through the following code.

    for(int i=0;i<model.rowCount();i++)
        {
             if(model.data(model.index(i),UAVDataModel::NameRole).toString() == "Initial Polygon Area" ){
                 connect(&av1,SIGNAL(initialPolygonAreaChanged(qreal)),&model,SIGNAL(dataChanged(model.index(i),model.index(i))));
             }
    
        }
    

    I am getting an error

    QObject::connect: No such signal DataModel::dataChanged(model.index(i),model.index(i))

    Note: I have not redeclared the signal in the model as it is inherited from QAbstractListModel


  • Moderators

    @saitej
    thats not how signal/slot connections work.
    Rather you should connect the model's dataChanged() signal to a slot of yours, and in the slot check the passed index if it's the row/column you want.



  • @raven-worx

    I am not editing the data, it is only displaying data from a source. So connecting datachanged signal to a slot does not make sense.

    we can use connect() to connect 2 signals right?


  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    So connecting datachanged signal to a slot does not make sense.

    sorry but that doesn't make sense. I think you still don't understand the concept.
    It doesn't matter if you edit the data or not.

    The dataChanged() signal notifies the receiver (-> slot) that the data of the given index(es) has changed, and the data needs to be retrieved from the model again.



  • @raven-worx

    I got what datachanged signal does. I would like to explain my problem better. Sorry for the inconvenience.

    For ex:
    I have a signal initialPolygonAreaChanged(qreal) emitted when ever initialPolygonArea is changed . I know the index of initialPolygonArea in the model. I need to connect the initialPolygonAreaChanged(qreal) with datachanged() signal. Is this the right way?


  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    Is this the right way?

    so far so good. You can also emit the dataChanged() signal whenever you emit initialPolygonAreaChanged(), since you know the index. (If the initialPolygonAreaChanged() signal is defined in the model)



  • @raven-worx

    It is not defined in the model so I was to trying to connect via this:

    connect(&av1,SIGNAL(initialPolygonAreaChanged(qreal)),&model,SIGNAL(dataChanged(model.index(i),model.index(i))));

    What should this be changed to?


  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    connect(&av1,SIGNAL(initialPolygonAreaChanged(qreal)),&model,SIGNAL(dataChanged(model.index(i),model.index(i))));

    This is not a valid connect statement. You are not allowed/supposed to do do a connection with the values already specified.

    You will have to do something like this:

    connect(&av1,SIGNAL(initialPolygonAreaChanged(qreal)),&model,SIGNAL(onInitialPolygonAreaChanged(qreal));
    

    In your model's onInitialPolygonAreaChanged(qreal) slot you then emit the dataChanged() signal with the correct indexes.



  • @raven-worx

    Oh .. !! I had thought of this but since there were lot of variables I was trying for a different way. I will go ahead this way.
    Thanks!!



  • @raven-worx

    Mine is a list with a single column with three roles. I need to update only the value role , as suggested I am, emitting datachanged signal via

    void DataModel::onInitialPolygonAreaChanged(qreal ){
        //0 is the index of IPA
        qDebug() << "IPA from model" ;
       QVector<int> roleVector(ValueRole);
    
        emit dataChanged(index(0),index(0),roleVector);
    }
    

    This also does not update the model.


  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    This also does not update the model.

    what do you mean?
    The dataChanged() signal just informs (the view) about the change. So you need to trigger this signal after the data for this role has changed.
    Who exactly is using the ValueRole?

    Please show the code of your model.



  • @raven-worx

    I have triggered it exactly when my data is being changed.

    My model:

    class DataList{
    public:
        DataList(const QString &name, const QString &value, const QString &category);
    
        QString name() const;
        QString value() const;
        QString category() const;
    
    private:
        QString m_name;
        QString m_value;
        QString m_category;
    };
    
    class DataModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        enum Roles {
            NameRole = Qt::UserRole + 1,
            ValueRole,
            CategoryRole
        };
    
        DataModel(QAbstractItemModel *parent = 0);
        virtual ~DataModel() {}
        void addData(const DataList &datalist);
        int rowCount(const QModelIndex & parent = QModelIndex()) const;
    
        QVariant data(const QModelIndex & index, int role=NameRole) const;
    protected:
        QHash<int, QByteArray> roleNames() const;
    
    private:
        QList<DataList> m_datalist;
    
    public slots:
        void onInitialPolygonAreaChanged(qreal);
    
    
    };
    
    

  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    I have triggered it exactly when my data is being changed.

    but who reads the ValueRole?! The view doesn't, it uses the `Qt::DisplayRole' by default.

    Also posting the header file of your model implementation is almost as good as providing nothing ;)



  • @raven-worx

    
    DataList::DataList(const QString &name, const QString &value, const QString &category){
        m_name = name;
        m_value = value;
        m_category = category;
    
    }
    
    QString DataList::name() const{
        return m_name;
    
    }
    
    QString DataList::value() const{
        return m_value;
    }
    
    QString DataList::category() const{
        return m_category;
    }
    
    
    DataModel::DataModel(QAbstractItemModel *parent) :
        QAbstractListModel(parent)
    {
    
    }
    
    void DataModel::addData(const DataList &datalist){
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_datalist << datalist;
    
    
    }
    
    int DataModel::rowCount(const QModelIndex &parent) const{
        Q_UNUSED(parent);
        return m_datalist.count();
    }
    
    QVariant DataModel::data(const QModelIndex &index, int role) const{
        if (index.row() < 0 || index.row() >= m_datalist.count())
                return QVariant();
        const DataList &datalist = m_datalist[index.row()];
    
        if(role ==NameRole )
            return datalist.name();
        else if(role == ValueRole)
            return datalist.value();
        else if(role == CategoryRole)
            return datalist.category();
    
        return QVariant();
    }
    
    QHash<int, QByteArray> DataModel::roleNames() const {
        QHash<int, QByteArray> roles;
        roles[NameRole] = "name";
        roles[ValueRole] = "value";
        roles[CategoryRole] = "category";
        return roles;
    }
    
    void DataModel::onInitialPolygonAreaChanged(qreal ){
        //0 is the index of iPA
        qDebug() << "IPA from model" ;
       QVector<int> roleVector(ValueRole);
    
        emit dataChanged(index(0),index(0),roleVector);
    }
    
    

  • Moderators

    @saitej

    1. DataModel constructor: not an issue, but i guess it's not necessary to require a QAbstractItemModel type as parent. I would change it to QObject
    2. you are not calling endInserteRows() in DataModel::addData() after you've appended the data.
    3. I ask one last time, since you are also not returning a DisplayRole value. How is your view using the ValueRole

    I don't see where the call to onInitialPolygonAreaChanged() comes from, and where the data update before this call happens.
    If you call addData() before triggering the onInitialPolygonAreaChanged() slot, all you need is to add the endInsertRows() call. If thats the case you also DO NOT need to make sure dataChanged() is triggered, because as i also already said, this is only necessary for EXISTING data changes. The updating on inserts is already handled by the beginInsertRows()/endInsertRows() calls.



  • @raven-worx

    I have initialized the model in the main window.h :

    DataModel model;
    

    In the mainwindow.cpp file

     model.addData(DataList(QString("Initial Polygon Area"),QString::number(uav1.initialPolygonArea()) + " sq.km",QString("Area")));
        connect(&uav1,SIGNAL(initialPolygonAreaChanged(qreal)),&model,SLOT(onInitialPolygonAreaChanged(qreal)));
    

    This is how I am calling onInitialPolygonAreaChanged slot.



  • @raven-worx said in Dynamic Upadate of data to a ListModel:

    I ask one last time, since you are also not returning a DisplayRole value. How is your view using the ValueRole

    Where should I return DisplayRole and why ?

    This is how I am using in the view:

    qml file:

     ListView {
                id: view
                anchors.top: parent.top
                anchors.bottom: parent.bottom
    
                width: parent.width
                model: UAVModel
                delegate: Text { text: name + "     " + value; font.pixelSize: 18 ; color: "white"}
    
                section.property: "category"
                section.criteria: ViewSection.FullString
                section.delegate: sectionHeading
            }
    

  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    Where should I return DisplayRole and why ?

    Nevermind, it took me a while realizing that we are here in the QML sub-forum. My bad.



  • @raven-worx

    So in QML, is there a different way to update the model or the same datachanged() signal should work?


  • Moderators

    @saitej
    yes, see this section.



  • @raven-worx

    Ya my code is completely based on this. They have also mentioned :

    QML views are automatically updated when the model changes. Remember the model must follow the standard rules for model changes and notify the view when the model has changed by using QAbstractItemModel::dataChanged(), QAbstractItemModel::beginInsertRows(), and so on. See the Model subclassing reference for more information.

    So I thought the model should be updated with datachanged() but it doesn't seem to. Any ideas to debug



  • @saitej use dataChanged() signal as was mentioned before.
    check if data() called after you have emitted the signal
    also I'm a bit skeptical about default role in QVariant data(const QModelIndex & index, int role=NameRole)
    anyway, check if data() gets called


  • Moderators

    @saitej said in Dynamic Upadate of data to a ListModel:

    So I thought the model should be updated with datachanged() but it doesn't seem to. Any ideas to debug

    post your model code and i tell you whats wrong



  • @raven-worx @vladstelmahovsky

    I got it working!! I had to add Qt::ItemIsEditable flag.

    Thanks!!


Log in to reply
 

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