Search code examples
pythonpyqtpysideqabstracttablemodelqitemdelegate

Adding row to QTableView with model and and delegate widgets


I am trying to add a row to QTableView with a QAbstractTableModel and QItemDelegate where the widgets appear in the added row. From what I've read I need to call .edit(index) on each item of the added row to call createEditor where the widgets are created however I am getting edit: editing failed

QItemDelegate:

class Delegate(QItemDelegate):

    def __init__(self):
        QItemDelegate.__init__(self)

        self.type_items = ["1", "2", "3"]

    def createEditor(self, parent, option, index):

        # COMBOBOX, LINEEDIT, TIMEDIT
        if index.column() == 0:
            comboBox = QComboBox(parent)
            for text in self.type_items:
                comboBox.addItem(text, (index.row(), index.column()))
            return comboBox

        elif index.column() == 1:
            lineEdit = QLineEdit(parent)
            return lineEdit

        elif index.column() == 2:
            timeEdit = QTimeEdit(parent)
            return timeEdit

    def setEditorData(self, editor, index):
        value = index.model()._data[index.row()][index.column()]

        if index.column() == 0:
            editor.setCurrentIndex(self.type_items.index(value))

        elif index.column() == 1:
            editor.setText(str(value))

        elif index.column() == 2:
            editor.setTime(value)

QAbstractTableModel:

class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data

    def data(self, index, role):
        pass

    def rowCount(self, index=None):
        return len(self._data)

    def columnCount(self, index=None):
        return len(self._data[0])

Main:

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        localWidget = QWidget()

        self.table = QTableView(localWidget)

        data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]

        self.model = TableModel(data)
        self.table.setModel(self.model)
        self.table.setItemDelegate(Delegate())

        self.add_row = QPushButton("Add Row", localWidget)
        self.add_row.clicked.connect(self.addRow)

        for row in range(self.model.rowCount()):
            for column in range(self.model.columnCount()):
                index = self.model.index(row, column)
                self.table.openPersistentEditor(index)

        layout_v = QVBoxLayout()
        layout_v.addWidget(self.table)
        layout_v.addWidget(self.add_row)
        localWidget.setLayout(layout_v)
        self.setCentralWidget(localWidget)
        self.show()

    def addRow(self):

        new_row_data = ["3", "Howdy", QTime(9, 0)]

        self.model.beginInsertRows(QModelIndex(), self.model.rowCount(), self.model.rowCount())
        self.model._data.append(new_row_data)
        self.model.endInsertRows()
        for i in range(len(new_row_data)):
            index = self.table.model().index(self.model.rowCount()-1, i)
            self.table.edit(index)

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

How can I trigger the QItemDelegate's createEditor to create the widgets for the added row's items and setEditorData to populate them?


Solution

  • First of all, you could use openPersistentEditor as you already did in the __init__.

    There are two reasons for the error in self.table.edit():

    1. in order to allow editing of an item through edit(), the flags() function must be overridden and provide the Qt.ItemIsEditable flag;
    2. you cannot call edit() multiple times on different indexes within the same function;

    Then, there are other important problems in your code:

    • you're not correctly using the model, as data() is not implemented;
    • the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good);
    • setEditorData is unnecessary, as long as the above aspects are respected: Qt automatically sets the data based on the data type and the editor class;
    • setData() should be implemented in order to correctly set the data on the model, otherwise the new data won't be accessible;
    • changes in the model structure (like creating a new row) should be done in the model class;

    Here is a revised version of your code:

    class Delegate(QItemDelegate):
        def __init__(self):
            QItemDelegate.__init__(self)
            self.type_items = ["1", "2", "3"]
    
        def createEditor(self, parent, option, index):
            if index.column() == 0:
                comboBox = QComboBox(parent)
                for text in self.type_items:
                    comboBox.addItem(text, (index.row(), index.column()))
                return comboBox
            # no need to check for the other columns, as Qt automatically creates a
            # QLineEdit for string values and QTimeEdit for QTime values;
            return super().createEditor(parent, option, index)
    
        # no setEditorData() required
    
    
    class TableModel(QAbstractTableModel):
        def __init__(self, data):
            super(TableModel, self).__init__()
            self._data = data
    
        def appendRowData(self, data):
            self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
            self._data.append(data)
            self.endInsertRows()
    
        def data(self, index, role=Qt.DisplayRole):
            if role in (Qt.DisplayRole, Qt.EditRole):
                return self._data[index.row()][index.column()]
    
        def setData(self, index, value, role=Qt.EditRole):
            if role == Qt.EditRole:
                self._data[index.row()][index.column()] = value
                self.dataChanged.emit(index, index)
                return True
            return False
    
        def rowCount(self, index=None):
            return len(self._data)
    
        def columnCount(self, index=None):
            return len(self._data[0])
    
        def flags(self, index):
            # allow editing of the index
            return super().flags(index) | Qt.ItemIsEditable
    
    
    class MainWindow(QMainWindow):
        # ...
        def addRow(self):
            row = self.model.rowCount()
    
            new_row_data = ["3", "Howdy", QTime(9, 0)]
            self.model.appendRowData(new_row_data)
    
            for i in range(self.model.columnCount()):
                index = self.model.index(row, i)
                self.table.openPersistentEditor(index)