How to modify the data in the model and update the view when new data is received from external?



  • Hello everyone,
    I have an model subclass from QAbstractmodel. I have created the model and set to the QTreeview. By default the values are empty. When the user left clicks the mouse set of new values arrives which has to be updated in the model and notify the view to refresh. In my model subclass i have a member function newValuesarrived(QMap<QString, QString> listOfNewValues); In this function i have to update the model and notify the view. How can this be achieved?

    ---- update---
    Due to legal reason i have to take out the code.


  • Lifetime Qt Champion

    Hi,

    You should start by reading the Model/View Programming chapter of Qt's documentation. You'll find the complete concept explained. As for refreshing the view, if you implement the model correctly, then it will be done automatically for you.



  • @SGaist : It does not explain about handling new data from extern source.



  • @VInay123
    It depends what you mean by "external source".

    • Whatever source you get data from, your job is to use the model update methods, and the view will get refreshed.
    • If you mean that new data arrives up at a database, Qt does not know about that.

    In my model subclass i have a member function newValuesarrived(QMap<QString, QString> listOfNewValues); In this function i have to update the model and notify the view. How can this be achieved?

    So you write the code to call methods like insertRows() & setData() to update the model from your new list of values. The view will get updated from the model.


  • Lifetime Qt Champion

    What external source do you have in mind ?



  • @SGaist I have uploaded the code. Actually i read a json file which has property nam, address, readonly and few more things. In tree form somethng like thi:
    ->Categoryitem1
    --->scenarioitem1(key and value)
    ---->scenarioitem2(key and value)

    Now i get new values in info variable which has to be updated in the scenarioitem1 and 2. Everytime user leftclicks i get new values which has to be updated in the view.


  • Lifetime Qt Champion

    Then update your model and then call the dataChanged signal.



  • @SGaist :I understand tht i need to send dataChanged signal. Where should i update my model? I mean at which place in my code? Secondly for dataChanged i need index values and how would i get it. If you could show me in my code will be helpful. Thankyou



  • @VInay123
    So when you read the JSON file that's where you will update your model.
    For the index values: yes, you have to decide where in your model your data is to go, and from that you will have the index values.



  • @JonB : As you see in my code i read the Json file only ones i..e during initialization with empty values. Later i just want to update the values but not read again the file. Second point how would you suggest me to implement in my design or code? Example would be nice. Thank you.


  • Lifetime Qt Champion

    Re-implement setData



  • @SGaist: How would i provide the index values? I am really sorry, because somehow i am not able to understand this trick of updating my data (categories_) in model, i.e. how do i get index from my data and call setData? I would really appreciate if you could show in my code? This would then later emit dataChanged which will achieve my goal.


  • Lifetime Qt Champion

    How exactly are you getting new data ?
    What are these new data ?
    One object update ?
    Several objects update ?
    How do they fit in your model internal representation ?



  • @SGaist : I am getting this data when user clicks left mouse button. I have slot, so when user clicks button some magic is done and later i save in model member variable Info_.
    The NewStructInfo looks like this:
    struct NewStructInfo{
    double x;
    double y;
    } Info_;
    So now my model data categories_ looks something like this:
    ```
    Property ----------------------------------------- Value
    -->Coordinates(categoryitem pointer)
    ------X (scenariopropertyitem1 pointer type) ------ "Empty"
    ------Y (scenariopropertyitem pointer type) -------- "Empty"

    The above structure you can see in parsejsonobject member function. Now in the above categories_ model data i have iterate along category->secnariopropertyitem and look for the name i,e,

    if(scenariopropertyitem->name == x)
    // scenariopropertyitem pointer has name as class member
    scenariopropertyitem->setValue(QVariant(Info_.X));
    Now somehow dataChanged(??,??,Qt::displayrole) has to be called.


  • Lifetime Qt Champion

    Since you have to go through your model to find the correct item to update, you should already have the index matching the scenariopropertyitem you found, no ?



  • @SGaist : How would i access it or get the index of the item? Meanwhile would you suggest me to use third option from this answer: https://stackoverflow.com/questions/29921041/getting-the-index-for-a-given-item-in-a-qtreeview-model-application.
    Could you provide an example?



  • This post is deleted!


  • This post is deleted!


  • What I always suggest is that if you are not 100% confident in your model design (and, believe me, it's a hard) just use QAbstractItemModel* model = new QStandardItemModel(parent). Then use insertRows, insertColumns, and setData to setup your model



  • In my Model.cpp i did the following:

        emit beginResetModel();
        for (auto& category : categories_) {
            category->entryAt(0)->setValue(1);
            // for testing  i just set randomly my data value to 1
        }
        emit endResetModel();
    

    But after above changes the view is not displaying the new values.



  • @VInay123
    I don't see any entryAt() function in Qt. So what type is category, what does category->entryAt(0)->setValue(1) do in the way of setting anything in the model??



  • @JonB: If you see my code Category is a class which has entryAt() function which returns back the propertyitem. So here i am setting the model data i.e. my categories_ member variable in the model class.



  • @VInay123
    OK for entryAt(). But where in your code does entryAt(0)->setValue(1) call QAbstractModel::setData() (http://doc.qt.io/qt-5/qabstractitemmodel.html#setData)? Where is the http://doc.qt.io/qt-5/qabstractitemmodel.html#dataChanged signal being emitted?



  • @JonB: That is the problem i am facing. I do not know how to call setData which in turn emits the signal dataChanged. I do not know how to get the indexes. Here the data is only coming from the "Info_" variable. If the it was via delegates or adapters i could easily get the indexes and call setData. But in my case i get in a structure and go through each item and update its value. Could you please show me how can i achieve this in my code i.e. change the value via setData?



  • @VInay123
    Your code is way too big for me to wade through.

    • If you are using QAbstractModel, you are using a model which holds data in rows & columns, right?

    • When you put some data (received from JSON parsing or whatever), you have to decide where in the model's row/column you want to put this data, right?

    • Given that row & column, you can construct a QModelIndex() (http://doc.qt.io/qt-5/qabstractitemmodel.html#createIndex) for the data.

    • Then you can pass that to your Model::setData() to set the value, and emit dataChanged(index, index) to notify your view to update.



  • @JonB : Thank you. I implemented as following in my model:

        QModelIndex index = createIndex(1, 1, categories_[0]->entryAt(0));
        this->setData(index, 11, Qt::EditRole);
    

    It is changing the value clearly. But in the view its not being displayed.



  • @VInay123
    Well at least we're getting somewhere! Check the return result of your setData(), maybe it's failing? Does (row, column) of (1, 1) exist in your model (else you need to insertRows() etc.)? Is that setData() call indeed going into your Model::setData() and doing the emit dataChanged() there (use a debugger or debug/print statements)? There's no point using both beginResetModel() & dataChanged(), and certainly not dataChanged() while still inside the beginResetModel() before the nedRestModel(). Change your code accordingly.



  • @VInay123 said in How to modify the data in the model and update the view when new data is received from external?:

    CategoryItem& parentItem = currentItem->parentItem();
    return createIndex(currentItem->row(), 0, &parentItem);

    This is using the address of a temp item, it will not work.

    QString data = file.readAll();

    does not handle win/lunix line endings and encodings, use QString data = QTextStream(&file).readAll();


    Use the model test to make sure your subclass works correctly



  • @JonB: Yes, I just call the setData ane here i have printed the changed:

        if (role == Qt::EditRole && index.column() == 1 && index.isValid()) {
            auto currentItem = static_cast<ScenarioPropertyItem*>(index.internalPointer());
            if (currentItem) {
                currentItem->setValue(value);
                qDebug() << currentItem->data(index.column());
                emit dataChanged(index, index);
                return true;
            }
        }
    

    The value is being changed here. The output looks like

    QVariant(int, 11)
    

    I have included a debug output in Data() but this is printing the default values that is empty("").

    QVariantModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const {
        if (!index.isValid()) {
            return QVariant();
        }
    
        if (role == Qt::DisplayRole && isCategory(index)) {
            const auto currentCategory = static_cast<CategoryItem*>(index.internalPointer());
            if (index.column() == 0) {
                return currentCategory->displayName();
            } else {
                return QVariant();
            }
        }
    
        const auto currentItem = static_cast<ScenarioPropertyItem*>(index.internalPointer());
        if (role == Qt::DisplayRole && !isCategory(index)) {
            qDebug() << currentItem->data(index.column()); // Here its empty
            return currentItem->data(index.column());
        }
        return QVariant();
    }
    
    


  • @VInay123
    At this point I don't know any more. Maybe someone else does. We have described how things work. You might like to set up a tiny much simplified example to test how it all works, or look on the web for other examples and start from there.



  • @VRonin: With the test i got an error:

      // Common error test #2, make sure that a second level index has a parent
        // that is the first level index.
    

    What does above mean and how does it fit in my code?



  • @VInay123 said in How to modify the data in the model and update the view when new data is received from external?:

    What does above mean and how does it fit in my code?

    It means that if model->hasChildren(parent) is true then index(0,0,parent).isValid() must be true and index(0,0,parent).parent()==parent.
    One of these 2 conditions is violated in your model.

    Again I strongly suggest you use QStandardItemModel rather than wasting time reimplementing your own



  • @VRonin: My class looks good:

    QModelIndex Model::parent(const QModelIndex& index) const {
        if (!index.isValid()) {
            return QModelIndex();
        }
    
        if (isCategory(index)) {
            return QModelIndex();
        }
    
        const auto currentItem = static_cast<ScenarioPropertyItem*>(index.internalPointer());
        CategoryItem& parentItem = currentItem->parentItem();
    
        return createIndex(currentItem->row(), 0, &parentItem); // Here the row is always 1.. is it right??
    }
    
    int Model::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const {
        if (parent.column() > 0) {
            return 0;
        }
    
        if (!parent.isValid()) {
            return static_cast<int>(categories_.size());
        }
    
        if (isCategory(parent)) {
            const auto currentCategory = static_cast<CategoryItem*>(parent.internalPointer());
            return currentCategory->entryCount();
        }
    
        return 0;
    }
    
    int Model::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const {
        return 2;
    }
    

    0_1535713717270_Capture.PNG

    Coordinates are my categories and S, T are scenariopropertiesitem. Items has category as parents. Categories dont have parents.



  • @VInay123 said in How to modify the data in the model and update the view when new data is received from external?:

    My class looks good:

    It doesn't. as I mentioned above

    @VRonin said in How to modify the data in the model and update the view when new data is received from external?:

    CategoryItem& parentItem = currentItem->parentItem();
    return createIndex(currentItem->row(), 0, &parentItem);

    This is using the address of a temp item, it will not work.



  • @VRonin: Oh.. How could this be fixed? I have to stick to QAbstractItemModel.
    This is from Qt:
    0_1535716942348_qq.PNG



  • My row method was wrong and after updating i have a new failure in the test:

                // Check that we can get back our real parent.
                //qDebug() << "TTT 1: " << model->parent(index);
                //qDebug() << "TTT 2: " << parent;
                //qDebug() << "TTT 3: " << index;
                tmp = model->parent(index);
                Q_ASSERT(tmp == parent);
    

    At the above point i.e. line 375 its failing.
    My old row function in item used to always give 1 which was wrong. Now i made it to look at the parent and find the position.

    int ScenarioPropertyItem::row() const {
        int count = parentItem_.entryCount();
        for (int i = 0; i < count; i++) {
            if (this->displayName_ == parentItem_.entryAt(i)->displayName_) {
                return i;
            }
        }
    }
    
    


  • @VInay123 How can parentItem_ not be a pointer? if you are taking a copy of the parent and store it in the child, you'll never go back to the old parent



  • @VRonin: It is taken as reference to an unique_pointer. Now i have changed it as following:

    QModelIndex Model::parent(const QModelIndex& index) const {
        if (!index.isValid()) {
            return QModelIndex();
        }
    
        if (isCategory(index)) {
            return QModelIndex();
        }
    
        const auto currentItem = static_cast<ScenarioPropertyItem*>(index.internalPointer());
        CategoryItem* parentItem = currentItem->parentItem();
    
        return createIndex(currentItem->row(), 0, parentItem);
    }
    

    Please have a look at the index implementation also:

    QModelIndex Model::index(int row, int column, const QModelIndex& parent /*= QModelIndex()*/) const {
        if (!hasIndex(row, column, parent)) {
            return QModelIndex();
        }
    
        if (!parent.isValid()) {
            return createIndex(row, column, categories_.at(row).get());
        }
    
        const auto currentCategory = static_cast<CategoryItem*>(parent.internalPointer());
        ScenarioPropertyItem* currentItem = currentCategory->entryAt(row);
    
        if (currentItem) {
            return createIndex(row, column, currentItem);
        } else {
            return QModelIndex();
        }
    }
    

    Tried the test again but its failing at 340 at the same place:
    0_1535718574820_qq.PNG

    I tried to comment in the debug output and i get the following:
    0_1535718651532_qq.PNG



  • This post is deleted!


  • @VInay123 said in How to modify the data in the model and update the view when new data is received from external?:

    I tried to comment in the debug output

    Good. If you check it you realise that the problem is that model->parent(index) returns an item with row==1 while the original parent had row==0. that's because in parent() you are return createIndex(currentItem->row(), 0, parentItem); instead of return createIndex(parentItem->row(), 0, parentItem);


Log in to reply
 

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