0

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?

Zoes
  • 29
  • 4

1 Answers1

1

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)
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • "setData() should be implemented in order to correctly set the data on the model, otherwise the new data won't be accessible". How to call setData() when the combobox is changed? – Zoes Jan 29 '21 at 04:23
  • @Zoes that depends on how you want the editor to behave and what data you want to set. Normally, Qt automatically calls `setData()` using the value based on the data type and the editor (as much as `setEditorData()` does) when the current index changes, and that happens because `setModelData` is called on the delegate. If you want to alter the result, you have to implement `setModelData` or connect the "value changed" signal of the editor with the appropriate function that eventually calls `setData()`: in your case, it might be necessary for the combobox using one of its signals. – musicamante Jan 29 '21 at 05:04
  • Be aware that, since you're setting custom user data for the combobox item, you probably need to correctly implement `setEditorData` too: your `data()` returns a simple string, but you're using custom data for the combo, and you might need to select the appropriate item, otherwise the default implementation would try to set the index based on its default property (normally, by looking up for the first match of `currentText`). I strongly suggest you to carefully study the documentation of delegates and models, do some experiments and eventually create a new question if you need clarifications. – musicamante Jan 29 '21 at 05:06