Search code examples
pythonqtpysideqabstracttablemodel

How to initialize QAbstractTableModel with an empty 2d array while preserving headers


I will demonstrate my problem with a small example:

import sys
from PySide import QtCore, QtGui

class TaskModel(QtCore.QAbstractTableModel):
    def __init__(self, tasks=[[" ", " ", " ", " "]]):
    # def __init__(self, tasks=[[]]):
        super().__init__()
        self.__tasks = tasks
        self.__headers = ["Folder", "Command", "Patterns", "Active", "Recursive"]

    def rowCount(self, *args, **kwargs):
        return len(self.__tasks)

    def columnCount(self, *args, **kwargs):
        coln = len(self.__tasks[0])
        return coln

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.__headers[section]
            else:
                # set row names: color 0, color 1, ...
                return "%s" % str(section+1)

    def data(self, index, role):
        row = index.row()
        col = index.column()
        value = self.__tasks[row][col]

        # text content
        if role == QtCore.Qt.DisplayRole:
            return value


    def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
        self.beginInsertRows(parent, position, position + rows - 1)
        row = ["a", "b", "c", "d"]
        for i in range(rows):
            self.__tasks.insert(position, row)
        self.endInsertRows()
        return True

class Mc(QtGui.QWidget):
    def __init__(self):
        super().__init__()
        self.tab = QtGui.QTableView()
        self.model = TaskModel()
        self.tab.setModel(self.model)
        self.addbtn = QtGui.QPushButton("Add")
        self.addbtn.clicked.connect(self.insert)
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.tab)
        layout.addWidget(self.addbtn)
        self.setLayout(layout)

    def insert(self):
        self.model.insertRow(0)

app = QtGui.QApplication(sys.argv)
mc = Mc()
mc.show()
sys.exit(app.exec_())

Note that I have two lines of __init__ function for the TaskModel class, the first one giving [[" ", " ", " ", " "]] as the default data set for __task, the second one giving [[]] instead.

The first one works fine:

enter image description here

except that there is a unwanted row sitting at the bottom.

The second __init__ function, while trying to remove that unwanted row, used [[]] as the default data set, but the result is disastrous:

enter image description here

How can I remove that unwanted bottom row while still making it work (with headers and everything)?


Solution

  • The reason why you have the extra row, is because you are forcing each instance of the table-model have one by setting it as the default argument. (Also, it is almost always a bug to use mutable objects as default arguments. If you do so, the same object will end up being shared between all instances of the class).

    The problem with the missing headers is caused by the way you have implemented columnCount. If the first task in the list is empty, the column count will be zero, and so no headers will be shown.

    Here's one way to fix these issues:

    class TaskModel(QtCore.QAbstractTableModel):
        def __init__(self, tasks=None):
            super().__init__()
            self.__tasks = [] if tasks is None else tasks
            self.__headers = ["Folder", "Command", "Patterns", "Active", "Recursive"]
    
        def rowCount(self, *args, **kwargs):
            return len(self.__tasks)
    
        def columnCount(self, *args, **kwargs):
            if self.__tasks:
                return len(self.__tasks[0])
            return len(self.__headers)