View not updating after appendRow



  • I have a model subclassed from QStandardItemModel, and viewing it using a view subclassed from QTableView. I connect the two via a proxy model subclassed from QIdentityProxyModel (I'm not sure if the proxy is relevant to the problem).

    I initially set up the model, adding rows to the model using appendRow. Then I set it as source for the proxy, and assign the proxy to the view. At this point the view works perfectly, showing the data from the model, reorganised as required by the proxy.

    Later I want to add new rows to the model. I use the same function that I used at initialisation, applying appendRow(newItem). But the view is not updated and does not show the new row. I know the row has been added to the model successfully since other functions which save the data to file save it correctly (the new row does show up after I relaunch the application and read in the saved data). model->rowCount() immediately shows the row has been added.

    Reading through posts on this forum about problems updating views, the solution generally seems to emit dataChanged by the model after appendRow, or add beginInsertRows and endInsertRows on either side of the call to appendRow. (Actually it's a bit confusing, when should beginInsertRows be used, and when should dataChanged be used? Or do they both have to be used at the same time?)

    To cover all bases, I've tried adding both beginInsertRows / endInsertRows and dataChanged where I use appendRow. But the new row is still not showing up in the view.
    I have connected the model's dataChanged signal to a slot in the proxy, and reemitted the dataChanged signal for the proxy. This is getting through, the proxy receives the model's dataChanged signal indicating the new row. But still the view is not updated.

    My appendRow function looks like this:
    (MyItem subclasses from QStandardItem)

    void MyModel::addEntry(MyItem *newItem)
    {
        newItem->setFlags(Qt::ItemIsEditable|newItem->flags());
        QModelIndex firstIndex=index(0,0);
        beginInsertRows(firstIndex.parent(),rowCount(),rowCount());
        this->appendRow (newItem);
        endInsertRows();
    
        QModelIndex newEndIndex=index(rowCount()-1,0);
        emit QStandardItemModel::dataChanged(firstIndex,newEndIndex);
    }
    
    

    The slot that I'm using in the proxy to register dataChanged looks like:

    void MyProxyTableModel::processDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
    {
        QModelIndex myBottom = mapFromSource(bottomRight);
        //  the proxy adds an extra dummy column filled with user data from the model
        QModelIndex myBottomRight = index( myBottom.row(), myBottom.column()+1, myBottom.parent() );
        emit dataChanged(mapFromSource(topLeft), myBottomRight,roles);
    }
    
    

    isValid() reports that all the indexes in this slot function are valid. The bottomRight has a row() indicating it's the new row corresponding to newEndIndex in addEntry( ).

    Any clues why the view is not receiving the notifications about the new row?
    Should I need to connect the model's beginInsertRows / endInsertRows for reprocessing by the proxy, just as I've done for dataChanged?



  • I attached the model directly to the view without a proxy (so only the DisplayRole is displayed, not my UserData). But the same problem happens even then. I can append a row to the model, but the view is still not updated to show the new row.

    So the problem here is not the proxy, or connecting signals to the proxy model.


  • Qt Champions 2016

    Hi
    @Ratzz had similar issue of view not showing a new row.
    He ended up calling
    emit layoutAboutToBeChanged();
    and
    emit layoutChanged();
    which did it for him.
    Not sure your case is the same but its worth mentioning.



  • Yeah, nah, I later saw Ratzz's thread and tried the layout signals. They didn't help either :(


  • Qt Champions 2016

    @Rizzer
    Hmm.
    Strange.
    Did you browse the
    http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html
    sample.
    It seems to use

    beginInsertRows(parent, position, position + rows - 1);   
    endInsertRows();
    

    (like you) and it updates. ?!
    (no dataChanged )

    Can it be some sort of index problem ? You say it does actually update the model but
    the view is clueless. So wonder if it can be something like that.



  • In my case I'm only adding 1 row, so "rows-1" would cancel, "beginInsertRows(parent, position, position)". For me, "position=rowCount()", intended to indicate I'm appending at the end of the model.

    Could there be a problem with parent? I'm not feeding any parent into my addEntry() function, I'm inferring a parent from firstIndex.parent(). I don't have a tree structure so I don't mean to have anything fancy with index parents.


  • Qt Champions 2016

    @Rizzer
    No really sure what it could be since it does store in model so if
    parent was off, would it still store?


  • Moderators

    You seem to be on the wrong track here. This

    beginInsertRows(firstIndex.parent(),rowCount(),rowCount());
    this->appendRow (newItem);
    endInsertRows();
    

    is very wrong. appendRow already calls begin/end so calling it around the call to appendRows will most definitely screw up the view. Also you shouldn't emit a dataChanged signal. It's for when data of existing items changes, not when new data is added. layoutChanged() is also for other purposes. This should be enough:

    void MyModel::addEntry(MyItem *newItem)
    {
        newItem->setFlags(Qt::ItemIsEditable|newItem->flags());
        appendRow (newItem);
    }
    

    If it's not then there's either something wrong with the connection or the view. Are you calling any disconnects on the model/view or blockSignals() at any point? Are you reimplementing any methods of the view? Is the item properly created when you add it (e.g. not on the stack and without parent)? Does it work with vanilla QTableView and QStandardItemModel?



  • Thanks Chris. I've been trying to put together a complete minimal test project. But my test project is awkwardly failing to reproduce the problem (i.e. the test view does successfully show the new row). You're right, in the test model I only need to use appendRow(newItem) alone, without the extra signals.

    I'm not using blockSignals. In my proxy in MyProxy::setSourceModel(newModel), if there's an existing sourceModel then I disconnect from the dataChanged signal from that sourceModel before calling QIdentityProxyModel::setSourceModel(newModel). Then I connect to dataChanged from newModel.

    Apart from that, the only use of disconnect in my code is disconnecting the timeout of a QTimer (not related to adding rows).

    In the view I'm reimplementing MyView::currentChanged(const QModelIndex &current, const QModelIndex &previous). I do that to set a data value before calling QTableView::currentChanged(current, previous).

    I create the item in another function of MyModel using MyItem *newItem = new MyItem(), and then pass it to addEntry(newItem). I'm not certain if the stack and parent is correct. Certainly I didn't create it using, say, "new MyItem(this)". Should I be doing that? But I didn't do it in my minimal test project either, where the view is working.


  • Moderators

    @Rizzer said:

    Should I be doing that?

    On the contrary. The item is taken over by the model so you shouldn't give it a parent.
    If it works with a simple example there's gotta be some glitch in your derived classes. Try to strip it down one by one until it works - first replace your item class with a QStandardItem, then the model and then the view. This should let you locate the faulty component.

    As for the proxy class - you said that it fails with or without it so it should not matter, but generally you don't need to manually connect/disconnect the dataChanged signal. It's done automatically by the classes provided in Qt. Unless of course you're connecting some customized methods of yours.



  • With the proxy, I'm converting a "1-column" model into a "2-column" model. I make proxy::data(index, role) with index.column=1 and role=Qt::DisplayRole return a user field drawn from the sourceModel (where column=0). When data in the sourceModel is updated, it emits dataChanged with column=0, so the view doesn't know about the update for column=1. So I connect to the dataChanged signal from the sourceModel and re-emit it for the proxy, altering proxyBottomRight to include column=1.

    For the rest of it, you're right, it looks like I'll just have to trawl through each class step by step.



  • @Rizzer said:

    I have a model subclassed from QStandardItemModel

    Have you tried append rows using itemFromIndex ? something like this ?

                    QStandardItem *itemToAdd = new QStandardItem(itemName);
                    model->itemFromIndex(modelIndex)->appendRow(itemToAdd);


  • No, that's actually worse, whether using model->itemFromIndex(modelIndex) or model->item(0). If I use existingItem->appendRow(newItem) then no rows added this way show up in the view at all, not even the ones initialised before the view is active.



  • It could be something in the Qt Designer ui form I was using. I've found that if I start afresh with a new form rebuilding the ui from scratch, then the view in the new ui does get updated straight away as expected.

    I still don't know precisely what was driving the problem, but this "workaround" will do as a solution.



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