Proper way of resizing model in QTableView



  • I have a QTableView that I'm populating with a subclass of QAbstractTableModel. The model is updated via a slot that receives the information and emits dataChanged. However, that doesn't seem to be enough for the view to show the new rows. I've tried also emitting layoutChanged after dataChanged in my slot and it works, but I'm not sure if that's correct. I've read the docs and dunno whether I should use beginInsertRows instead.

    I want to know if I'm doing it correctly or there's a different way.

    Anyway, here's a minimal compilable example for you to try, you can just copy and paste it in a .cpp and compile it.

    #include <QApplication>
    #include <QAbstractTableModel>
    #include <QDateTime>
    #include <QVBoxLayout>
    #include <QTimer>
    #include <QDebug>
    
    class MyModel : public QAbstractTableModel
    {
    public:
        int rowCount(const QModelIndex &parent) const { return mRecords.count(); }
        int columnCount(const QModelIndex &parent) const { return 2; }
    
        QVariant data(const QModelIndex &index, int role) const
        {
            if (role != Qt::DisplayRole)
                return QVariant();
    
            Record r = mRecords.at(index.row());
    
            return index.column() == 0 ? r.date : r.value;
        }
    
        QVariant headerData(int section, Qt::Orientation orientation, int role) const
        {
            if (orientation != Qt::Horizontal)
                return QVariant();
    
            if (role != Qt::DisplayRole)
                return QVariant();
    
            if (section == 0)
                return QString("Date");
    
            else if (section == 1)
                return QString("Number");
    
            return QVariant();
        }
    
    public slots:
        void addValue()
        {
            Record r = {
                QDateTime::currentDateTime().toString(),
                QString::number(qrand() % 1000)
            };
    
            mRecords << r;
    
            qDebug() << "Added record" << r.date << r.value;
    
            emit dataChanged(index(mRecords.count() - 1, 0), index(mRecords.count() - 1, 1));
            emit layoutChanged();
        }
    
    
    private:
        struct Record {
            QString date;
            QString value;
        };
    
        QList<Record> mRecords;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QWidget w;
    
        QTableView * table = new QTableView(&w);
        MyModel model;
        table->setModel(&model);
        QVBoxLayout * layout = new QVBoxLayout(&w);
        layout->addWidget(table);
    
        QTimer t;
        QObject::connect(&t, &QTimer::timeout, [&] { model.addValue(); });
        t.start(250);
    
        w.show();
    
        return a.exec();
    }
    
    


  • @JoseTomasTocino
    I don't know about your code. But dataChanged signal must only be emitted for existing rows whose content is changed. You must emit begin/endInsert/DeleteRows instead for row inserts/deletes to make it work correctly.



  • I already stated in my post that I think I should use beginInsertRows / endInsertRows, I was asking for the proper way of doing it.


  • Qt Champions 2018

    http://doc.qt.io/qt-5/qabstractitemmodel.html#subclassing

    Models that provide interfaces to resizable data structures can provide implementations of insertRows(), removeRows(), insertColumns(),and removeColumns(). When implementing these functions, it is important to notify any connected views about changes to the model's dimensions both before and after they occur:

    • An insertRows() implementation must call beginInsertRows() before inserting new rows into the data structure, and endInsertRows() immediately afterwards.
    void addValue()
        {
            Record r = {
                QDateTime::currentDateTime().toString(),
                QString::number(qrand() % 1000)
            };
    beginInsertRows(QModelIndex(),mRecords.size(),mRecords.size());
            mRecords << r;
    endInsertRows();
            qDebug() << "Added record" << r.date << r.value;
        }
    

    P.S.
    Make your model go through the model test there's a couple of points that should get triggered by that test in your model



  • @VRonin That's really useful, thank you very much. Also I didn't know about the "Model test", that's nice too.