Search code examples
pythonpyqtqtableviewqabstractitemmodelqabstracttablemodel

How to insert a new column to a QAbstractTable Model at runtime using PYQT


I have created a model class of type QAbstractTableModel to which I have added a number of methods as shown here:

class resultsModel(QAbstractTableModel):
    def __init__(self, parent, headerData, arraydata, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.arraydata = arraydata
        self.headerdata = headerData #['Timestamp', 'Force (N)', 'Diplacement (mm)']
    
    def rowCount(self, parent):
        return len(self.arraydata)

    def columnCount(self, parent):
        if len(self.arraydata) > 0: 
            return len(self.arraydata[0]) 
        return 0
        
    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.headerdata[col]
        return None
    
    #Make table cells non-editable
    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable   

    def data(self, index, role):
        if not index.isValid():
            return None
        value = self.arraydata[index.row()][index.column()]    
        if role == Qt.EditRole:
            return value
        elif role == Qt.DisplayRole:
            return value

    def setData(self, index, value, role):
        if not index.isValid():
            return None
        row = index.row()
        column = index.column()
        self.arraydata[row][column] = value
        self.dataChanged.emit(index, index)
        return True
        
    def setHeaderData(self, col, orientation, value, role):
        if role != Qt.DisplayRole or orientation != Qt.Horizontal:
            return False

        self.headerdata[col] = value
        result = self.headerdata[col]
        if result:
            self.headerDataChanged.emit(orientation, col, col)
        
        return result

When the application starts I use a QTableview with the model above behind it:

resultsHeaders = ['Timestamp', 'Force (N)', 'Diplacement (mm)']
resultsData = [['','','']]
self.resultsTableModel = resultsModel(self, resultsHeaders, resultsData)
self.resultsTable = QTableView()
self.resultsTable.setModel(self.resultsTableModel)

During runtime, if a serial device is connected, I want to add some additional columns to the model but I am having difficulty implementing an 'insertColumns' method for the model before I add the new header values.

#insert columns
???
#update header values
for i in range(len(resultsHeaders)):            
    self.resultsTable.model().setHeaderData(i, Qt.Horizontal, str(resultsHeaders[int(i)]), Qt.DisplayRole)

Solution

  • An appropriate "insert column(s)" function must be implemented, as the model should correctly be updated to reflect the actual column count, so that the new headers are also correctly shown.

    In order to correctly add columns, both beginInsertColumn() (before adding new column data) and endInsertColumn() (at the end of the operation) should be properly called.
    Normally, adding more columns would require to implement insertColumns(), but considering the situation and the fact that they would call both methods anyway, there's no need to do that, and a custom function will be fine.

    The following only inserts a single column, if you want to add more columns at once, just ensure that the third argument of beginInsertColumns correctly reflects that (newColumn + len(columnsToAdd)) and that the header data and new empty values for the array correspond to the new column count.

    class ResultsModel(QAbstractTableModel):
        # ...
        def addColumn(self, name):
            newColumn = self.columnCount()
            self.beginInsertColumns(QModelIndex(), newColumn, newColumn)
            self.headerdata.append(name)
            for row in self.arraydata:
                row.append('')
            self.endInsertColumns()
    
    # ...
    class Whatever(QWidget):
        # ...
        def addColumn(self):
            name = 'New column {}'.format(self.resultsTableModel.columnCount())
            self.resultsTableModel.addColumn(name)
    

    Note that:

    1. both rowCount() and columnCount() should have a default keyword parent argument as the base implementation does (accepted practice involves parent=QModelIndex());
    2. class names should always be capitalized, so you should rename your model class to ResultsModel; read more on the topic on the official Style Guide for Python Code;