Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QAbstractTableModel and dataChanged



  • Hi all,

    I've maybe a really stupid question / error here, which drives me crazy.

    I've read the documentation about read only models and there is nothing really concret about how to update the data. It's said, that on changes I've to emit dataChanged, which in the end doesn't update my view.

    Here the model cpp code:

    int SqlTableModel::rowCount( const QModelIndex& ) const
    {
        return _data.count();
    }
    
    int SqlTableModel::columnCount( const QModelIndex& ) const
    {
        if( _data.count() > 0 )
            return _data.first()._data.count();
        else
            return 0;
    }
    
    QVariant SqlTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
    {
        if( role != Qt::DisplayRole || orientation != Qt::Horizontal )
            return QVariant();
        return _header.at( section );
    }
    
    QVariant SqlTableModel::data( const QModelIndex& index, int role ) const
    {
        if( !index.isValid() )
            return QVariant();
        if( role != Qt::DisplayRole )
            return QVariant();
        if( index.row() < _data.count() )
        {
            if( index.column() < _data[index.row()]._data.count() )
                return _data[index.row()]._data[index.column()];
            else
                return QVariant();
        }
        else
            return QVariant();
    }
    
    void SqlTableModel::setQuery( QString queryStr )
    {
        _queryStr = queryStr;
        SQL::execQuery( queryStr );
    
        QSqlQuery query = SQL::getLastQuery();
    
        int row = 0;
        QSqlRecord rec = query.record();
        int colCount = rec.count();
    
        _data.clear();
        
        while( query.next() )
        {
            SqlDataStruct stuc;
            for( int column = 0; column < colCount; column++ )
            {
                stuc._data.insert( column, query.value( column ).toString() );
            }
            _data.insert( row, stuc );
            row++;
        }
        endInsertRows();
    
        QModelIndex topLeft = index( 0, 0 );
        QModelIndex bottomRight = index( row-1, colCount-1 );
    
        emit dataChanged( topLeft, bottomRight );
    }
    

    Here the usage:

        _compModel = new SqlTableModel;
        _compView = new QTableView;
        _compView->setModel( _compModel );
        _compModel->setQuery( queryStr );
    
    

    So, what is needed to update the view correctly after "setQuery"? Do I have to use "begin/end/InsertRows" thingy?

    Best regards



  • @Lachrymology
    Before you go any further: from your use of SQL & Sql (you don't tell us what they are derived from) all over the place, if this is to serve a SQL database why are you attempting to use QAbstractTableModel etc. when all the stuff is already available to you from the QSql... classes?

    If you were using, say, QSqlTableModel your SqlTableModel::setQuery() wouldn't look anything like whatever you are doing here. In fact, none of it would, it's written for you!



  • @Lachrymology said in QAbstractTableModel and dataChanged:

    It's said, that on changes I've to emit dataChanged, which in the end doesn't update my view.

    because you are not changing existing data but inserting new data. you should call beginInsertRows/endInsertRows and beginInsertColumns/endInsertColumns.
    In any case follow @JonB 's advice and use QSqlQueryModel or QSqlTableModel



  • @JonB
    I'm aware of QSqlTableModel, but I was upset about the setTable function, therefore I tried to wrote my own model. Furthermore later on I discovered the setQuery method .. mhh .. maybe I derive later on from this model. Nevertheless I needed to understand what's happening and why.

    @VRonin
    Okay, that was what I was looking for. So I need to use the begin / insert / end system. Thanks, I will try it.



  • @Lachrymology
    I will just say that there is a lot more code going on in QSqlQuery..., QSqlTableModel et al code that what you begin to show in your QAbstractTableModel attempt. Do whatever you like to learn, but consider using the supplied classes before too long....!



  • @JonB
    Jep, subclassing QSqlTableModel is easy and it works like expected. I've tried it now.

    Nevertheless - I don't get it .. the docs are saying the following:

    "When subclassing QAbstractItemModel, at the very least you must implement index(), parent(), rowCount(), columnCount(), and data(). These functions are used in all read-only models, and form the basis of editable models."

    Currently I've a read-only model. As shown above I've implemented these function, except index and parent.

    But I'm not sure about:
    "Models that provide interfaces to resizable data structures can provide implementations of insertRows(), removeRows(), insertColumns(),and removeColumns()."

    The "CAN" confuses me - do I have to or is it optional?

    Furthermore I'm now working with: beginInsertRows/endInsertRows and beginInsertColumns/endInsertColumns

    void SqlTableModel::setHeader( QStringList header )
    {
        _header = header;
        beginInsertColumns( QModelIndex(), 0, _header.count() );
        int c = 0;
        for( QString headStr : _header )
        {
            qDebug() << "Inserted Column: " << insertColumn( c, QModelIndex() );
            c++;
        }
        endInsertColumns();
    }
    
    void SqlTableModel::setQuery( QString queryStr )
    {
        _queryStr = queryStr;
        SQL::execQuery( queryStr );
    
        QSqlQuery query = SQL::getLastQuery();
    
        int row = 0;
        QSqlRecord rec = query.record();
        int colCount = rec.count();
    
        _data.clear();
    
        beginInsertRows( QModelIndex(), 0, query.size() );
        while( query.next() )
        {
            SqlDataStruct stuc;
            for( int column = 0; column < colCount; column++ )
            {
                stuc._data.insert( column, query.value( column ).toString() );
            }
            _data.insert( row, stuc );
            qDebug() << "Inserted Row: " << insertRow( row );
            row++;
        }
        endInsertRows();
    
        QModelIndex topLeft = index( 0, 0 );
        QModelIndex bottomRight = index( row-1, colCount-1 );
    
        qDebug() << topLeft.isValid();
        qDebug() << bottomRight.isValid();
    
        emit dataChanged( topLeft, bottomRight );
    }
    

    insertColumn and insertRow are both always false.

    So, what I'm missing now?
    layoutChanged, modelReset?


  • Qt Champions 2019

    @Lachrymology said in QAbstractTableModel and dataChanged:

    So, what I'm missing now?

    Why do you reimplement setQuery() at all? There is no need for it.



  • Uff ... I think it would be easier to have the SQL thingy removed first.

    So okay, let us assume SQL stands here for Super Quarreler Laughter, which is a complex data structure needing a TableModel to be presented. Deal?



  • @Lachrymology said in QAbstractTableModel and dataChanged:

    But I'm not sure about:
    "Models that provide interfaces to resizable data structures can provide implementations of insertRows(), removeRows(), insertColumns(),and removeColumns()."
    The "CAN" confuses me - do I have to or is it optional?

    Yes, you have to. Read e.g. https://doc.qt.io/qt-5/qabstractitemmodel.html#insertRows

    Note: The base class implementation of this function does nothing and returns false.

    On models that support this, inserts count rows into the model

    The base does nothing but return false, so no insertion/deletion of rows or columns. If you have a mechanism for inserting rows into your data --- which you do --- then to make that available you must implement this, and the other virtuals you support.

    Also it concludes

    If you implement your own model, you can reimplement this function if you want to support insertions. Alternatively, you can provide your own API for altering the data. In either case, you will need to call beginInsertRows() and endInsertRows() to notify other components that the model has changed.



  • @JonB
    I'm confused.

    I have a data set/list.

    Via the methods "rowCount" and "columnCount" the model provides how many rows/columns are expected.

    Via "headerData" the model provides what the names of the head section are.

    Via the method "data" the model is iterating over the rows and columns.
    Within this it accesses the data set/list and provides it for each tupel.

    So far what my knowledge is, about why these are the major methods for read-only models.

    If I'm using, like in the beginning, only these methods, nothing will be displayed/updated/inserted.


    Ok the base of insertRow(s) returns false. In many examples this is mainly used to store external data within the internal data set/list without doing anything else than calling begin/endInsertRows. Same with insertColumn(s)

    This is what I'm doing within "setQuery" and "setHeader".


    If I'm looking at this example:
    https://doc.qt.io/archives/4.6/itemviews-addressbook-tablemodel-cpp.html

    I don't see, what I'm missing to have my model working with the QTableView.


    So again - why do I need exactly to implement such methods?



  • Found it on my own:

    Within setQuery I missed calling "begin/endResetModel".

    void SqlTableModel::setQuery( QString queryStr )
    {
        _queryStr = queryStr;
        SQL::execQuery( queryStr );
    
        QSqlQuery query = SQL::getLastQuery();
    
        int row = 0;
        QSqlRecord rec = query.record();
        int colCount = rec.count();
    
        beginResetModel();
        _data.clear();
    
        beginInsertRows( QModelIndex(), 0, query.size() );
        while( query.next() )
        {
            SqlDataStruct stuc;
            for( int column = 0; column < colCount; column++ )
            {
                stuc._data.insert( column, query.value( column ).toString() );
            }
            _data.insert( row, stuc );
            qDebug() << "Inserted Row: " << insertRow( row );
            row++;
        }
        endInsertRows();
        endResetModel();
    
        QModelIndex topLeft = index( 0, 0 );
        QModelIndex bottomRight = index( row-1, colCount-1 );
    
        qDebug() << topLeft.isValid();
        qDebug() << bottomRight.isValid();
    
        emit dataChanged( topLeft, bottomRight );
    }
    

  • Qt Champions 2019

    I still don't understand why you implement your own setQuery() function...

    /edit: and beginInsertRows()/endInsertRows() is not needed (and may kill something internally) since you already call being/endResetModel(). Cascading those calls is not supported (and not needed at all)



  • @Christian-Ehrlicher
    Thanks for the hint.


Log in to reply