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

Subclassing QAbstractTableModel



  • A few days ago I decided to switch from using table Widgets to the table model/view system and I'm struggling to understand what I actually need to implement when subclassing QAbstractTableModel and what the various functions do.

    My current understanding is that rowCount, columnCount, and data must be implemented. When the view accesses the model it will check the rowCount and columnCount and then execute data row*column times. I don't have much use headers in my table so I won't think about headerData for now.

    Currently my application takes user input to create a matrix with row and column numbers taken from user input. The user then inputs values which are stored in a matrix (I'm using the Eigen library's Eigen::MatrixXd, a dynamically sized matrix of doubles). This data type is essentially a vector<vector<double>>. After reading the many documentations of the model/view system, I have no idea how to put my data into the model. Is this what the setData function does? Do I also need to implement insertRow, insertColumn, removeRow, removeColumn?

    I have been trying to create a subclass that has a private matrix that is filled with a member function called Input of the model class. rowCount returns the row number of this matrix, colCount returns column number of the matrix and data returns the value at row, col for the matrix. I cannot get it to work and I think my understanding of what I am trying to do is flawed, so I'm seeking help and taking a break.
    Thanks for any and all help!


  • Moderators

    @sheetkey said in Subclassing QAbstractTableModel:

    After reading the many documentations of the model/view system, I have no idea how to put my data into the model.

    The easiest way to put data into your custom model is to implement a class method like MyMatrixModel::setMatrix(Eigen::MatrixXd mat). In this function, store the matrix in your model as a private member variable and emit the dataChanged() signal to tell the View to refresh.

    Then...

    • Reimplement rowCount() and columnCount() to return the dimensions of your internal matrix.
    • Reimplement data() to return the value at the coordinates of your matrix:
    QVariant MyMatrixModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const
    {
        if (role == Qt::DisplayRole || role == Qt::EditRole)
            return m_matrix(index.row(), index.column());
    
        // Don't return matrix element values for other
        // roles like FontRole or BackgroundColorRole
        return QVariant();
    }
    

    Is this what the setData function does?

    setData() is used by the View class to edit individual elements. Implement it if you want to let the user update values through the View GUI.

    Do I also need to implement insertRow, insertColumn, removeRow, removeColumn?

    Only if you want the ability to change your matrix dimensions at runtime.



  • @JKSH Thank you very much. I think I have correctly reimplemented some of the functions and have created a setMatrix function as you suggested, called Input. Do I have to reimplement the dataChanged signal or connect the signal to a slot that tells the view to refresh?

    tablemodelcalss.h

    #ifndef TABLEMODELCLASS_H
    #define TABLEMODELCLASS_H
    
    #include <QAbstractTableModel>
    #include <QObject>
    #include <Eigen>
    #include <cassert>
    
    class TableModelClass : public QAbstractTableModel
    {
        Q_OBJECT
    public:
        TableModelClass(QObject *parent = 0);
    
        void Input(Eigen::MatrixXd *InMat);
    
        int rowCount(const QModelIndex &parent) const;
        int columnCount(const QModelIndex &parent) const;
        QVariant data(const QModelIndex &index, int role) const;
    
    private:
        Eigen::MatrixXd *m_matrix;
        int Rows;
        int Columns;
    
    signals:
        void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
    
    };
    
    #endif // TABLEMODELCLASS_H
    
    

    tablemodelcalss.cpp

    #include "tablemodelclass.h"
    
    TableModelClass::TableModelClass(QObject *parent) : QAbstractTableModel(parent)
    {
    
    }
    void TableModelClass::Input(Eigen::MatrixXd *InMat)
    {
        Q_ASSERT(InMat);
    
        Rows = InMat->rows();
        Columns = InMat->cols();
    
        m_matrix->resize(Rows, Columns);
    
        m_matrix = InMat;
    
        emit dataChanged(index(0, 0), index(Rows, Columns));
    }
    int TableModelClass::rowCount(const QModelIndex &parent) const
    {
        if(parent.isValid() || !m_matrix)
        {
                return 0;
        }
        return m_matrix->rows();
    }
    int TableModelClass::columnCount(const QModelIndex &parent) const
    {
        if(parent.isValid() || !m_matrix)
        {
            return 0;
        }
        return m_matrix->cols();
    }
    QVariant TableModelClass::data(const QModelIndex &index, int role) const
    {
        if(!index.isValid())
        {
            return QVariant();
        }
        if(index.row() >= m_matrix->rows() || index.row() < 0)
        {
            return QVariant();
        }
        if(role == Qt::DisplayRole)
        {
            return m_matrix->coeff(index.row(), index.column());
        }
        return QVariant();
    }
    
    

  • Moderators

    @sheetkey said in Subclassing QAbstractTableModel:

    I think I have correctly reimplemented some of the functions and have created a setMatrix function as you suggested, called Input.

    Yep, your code looks correct to me.

    A few observations:

    • Since you are storing Eigen::MatrixXd *, it is your responsibility to make sure that the matrix object doesn't get destroyed or modified externally. A more robust design would be to only construct/edit/destroy the matrix within TableModelClass instead of passing a pointer into setMatrix()/Input().
    • You don't need to store the Rows and Columns variables; just call m_matrix-rows() and m_matrix->cols().
    • It is good practice to add the override keyword to reimplemented functions.

    Do I have to reimplement the dataChanged signal

    No. In fact, you cannot reimplement signals because they are not virtual.

    Remove dataChanged() from tablemodelcalss.h and just emit QAbstractItemModel::dataChanged().

    or connect the signal to a slot that tells the view to refresh?

    No need. The View auto-connects the model's signals to its slots when you call QAbstractItemView::setModel().



  • @JKSH
    Thanks again for the help. I've cleaned up my code and have done some testing but I'm still missing something. The view does not update after Inputting a matrix into the model. The view is always a blank table.
    I have tried changing the model to only have 2 rows and 2 columns to check if there is an issue with the rowCount and columnCount reimplementation. The only issue I can think of with these is if the parent is not valid, though to be frank I'm not sure what that would mean.
    The other thing I can think of is that I need to reimplement insertRows and have the model add the number of rows and columns after I have Input a matrix.

    tablemodelclass.h

    #ifndef TABLEMODELCLASS_H
    #define TABLEMODELCLASS_H
    
    #include <QAbstractTableModel>
    #include <QObject>
    #include <Eigen>
    #include <cassert>
    
    class TableModelClass : public QAbstractTableModel
    {
        Q_OBJECT
    public:
        TableModelClass(QObject *parent = 0);
    
        void Input(Eigen::MatrixXd InMat);
    
        int rowCount(const QModelIndex &parent) const override;
        int columnCount(const QModelIndex &parent) const override;
        QVariant data(const QModelIndex &index, int role) const override;
    
    private:
        Eigen::MatrixXd m_matrix;
    
    };
    
    #endif // TABLEMODELCLASS_H
    

    tablemodelclass.cpp

    #include "tablemodelclass.h"
    
    TableModelClass::TableModelClass(QObject *parent) : QAbstractTableModel(parent)
    {
    
    }
    
    void TableModelClass::Input(Eigen::MatrixXd InMat)
    {
        m_matrix.resize(InMat.rows(), InMat.cols());
    
        m_matrix = InMat;
    
        emit QAbstractItemModel::dataChanged(index(0, 0), index(m_matrix.rows() - 1, m_matrix.cols() - 1));
    }
    
    int TableModelClass::rowCount(const QModelIndex &parent) const
    {
        if(!parent.isValid())
        {
        return 0;
        }
        return m_matrix.rows();
    }
    int TableModelClass::columnCount(const QModelIndex &parent) const
    {
        if(!parent.isValid())
        {
        return 0;
        }
        return m_matrix.cols();
    }
    QVariant TableModelClass::data(const QModelIndex &index, int role) const
    {
        if(!index.isValid())
        {
            return QVariant();
        }
        if(index.row() >= (m_matrix.rows() - 1) || index.row() < 0)
        {
            return QVariant();
        }
        if(role == Qt::DisplayRole)
        {
            return m_matrix(index.row(), index.column());
        }
        return QVariant();
    }
    

    For my testing I have have used:

        Eigen::MatrixXd testmat;
        testmat.resize(2,2);
        testmat << 1, 2, 3, 4;
    
        TableModelClass *OG_Model = new TableModelClass(this);
        OG_Model->Input(testmat);
        ui->tableViewOG_3->setModel(OG_Model);
    

    within the mainwindow ui creation, but eventually I will input the matrix with a push button. I have tried inputting testmat after setting the model but this had no effect.


  • Moderators

    You're welcome :)

    @sheetkey said in Subclassing QAbstractTableModel:

    The only issue I can think of with these is if the parent is not valid, though to be frank I'm not sure what that would mean.
    ...

    int TableModelClass::rowCount(const QModelIndex &parent) const
    {
        if(!parent.isValid())
       {
           return 0;
       }
       return m_matrix.rows();
    }
    

    Your logic is now different from your previous post. It should return 0 if parent is valid.

    In Qt models, an index has a parent if it is nested under another index. Tree models use nesting, but table models do not. Therefore, an index from a table model should always have an invalid parent.

    If your code still doesn't work as expected, put qDebug() calls inside the functions to see what values are being returned to the View.



  • @JKSH It works! with a caveat: I have to

    model->Input(matrix);
    

    before I can

    ui->tableView->setModel(model);
    

    I'm currently declaring a model in mainwindow.h and implementing it in the usual place within the ui section of mainwindow.cpp (I'm not sure what it's called, but it's the equivalent to a main function). The view does not get updated if I set the model for the view right after implementing the model as a new TableModelClass. I have to set the model later in the program after the model has data. This makes me think that the emit dataChanged signal is not working correctly. This is not a problem for my current use of model/view, but for the future, how can I fix this so that the view has the model before I input data?


  • Moderators

    @sheetkey said in Subclassing QAbstractTableModel:

    It works!

    Congrats!

    This makes me think that the emit dataChanged signal is not working correctly.

    Oops, I was thinking of the wrong signal. Instead of emitting dataChanged()...

    1. Call beginResetModel()
    2. Update m_matrix
    3. Call endResetModel()

    See https://doc.qt.io/qt-5/qabstractitemmodel.html#beginResetModel



  • @JKSH Thank you very much for the help these past few days!

    As a conclusion to this thread, for anyone who may stumble upon it in the future, the working code:

    tablemodelcalss.h

    #ifndef TABLEMODELCLASS_H
    #define TABLEMODELCLASS_H
    
    #include <QAbstractTableModel>
    #include <QObject>
    #include <Eigen>
    #include <cassert>
    
    class TableModelClass : public QAbstractTableModel
    {
        Q_OBJECT
    public:
        TableModelClass(QObject *parent = 0);
    
        void Input(Eigen::MatrixXd InMat);
    
        int rowCount(const QModelIndex &parent) const override;
        int columnCount(const QModelIndex &parent) const override;
        QVariant data(const QModelIndex &index, int role) const override;
    
    private:
        Eigen::MatrixXd m_matrix;
    
    };
    
    #endif // TABLEMODELCLASS_H
    

    tablemodleclass.cpp

    #include "tablemodelclass.h"
    
    #include <QDebug>
    
    TableModelClass::TableModelClass(QObject *parent) : QAbstractTableModel(parent)
    {
    
    }
    
    void TableModelClass::Input(Eigen::MatrixXd InMat)
    {
        beginResetModel();
    
        m_matrix.resize(InMat.rows(), InMat.cols());
    
        m_matrix = InMat;
    
        endResetModel();
    }
    
    int TableModelClass::rowCount(const QModelIndex &parent) const
    {
        if(parent.isValid())
        {
        return 0;
        }
        return m_matrix.rows();
    }
    int TableModelClass::columnCount(const QModelIndex &parent) const
    {
        if(parent.isValid())
        {
        return 0;
        }
        return m_matrix.cols();
    }
    QVariant TableModelClass::data(const QModelIndex &index, int role) const
    {
        if(!index.isValid())
        {
            return QVariant();
        }
        if(index.row() >= (m_matrix.rows()) || index.row() < 0)
        {
            return QVariant();
        }
        if(role == Qt::DisplayRole)
        {
            return m_matrix(index.row(), index.column());
        }
        return QVariant();
    }
    

Log in to reply