[Python] Preventing unexpected trigger of setModelData and setData in QTableView with...

Discussão em 'Python' iniciado por Stack, Agosto 8, 2025.

  1. Stack

    Stack Membro Participativo

    The code below mostly does what I want: I start out with data of 2D table (list of lists), where 1st and 3rd column start out as random numbers, but 2nd column starts with None. This data is used for a table model of a QTableView, which for the cells in the 2nd None column, displays QComboBox with several entries that are also random numbers. The idea is then, that making a choice of a value in a QComboBox sets the corresponding integer value in the 2D table at the given row and column. This is done well by the combo.currentIndexChanged.connect... line.

    However, I have commented that combo.currentIndexChanged.connect... line in the code below, just to emphasize the weird behavior that I'm seeing.

    First, I'd like to note something that was not obvious to me, which is that

    • Without openPersistentEditor, the corresponding cell will never run ComboBoxDelegate.createEditor (and ComboBoxDelegate.setModelData), and correspondingly never show dropdowns/QComboBoxes.
    • Cells that do not have openPersistentEditor, but return flags with Qt.ItemIsEditable, will still allow for double-click and edit via the default line edit (and will only call setData on change).

    Now, then, the weird behavior is:

    • I start the code, I get the window rendered with the table, and I immediately close the application. In the printout, I get:

    setModelData self.sender()=None index.row()=2 index.column()=1 editor.currentText()='70'
    setData self.sender()=None row=2 col=1 value='70'
    ... self._data=[[67, None, 41], [99, None, 5], [27, '70', 47]]


    So, without any interaction at all, somehow both QStyledItemDelegate.setModelData and QAbstractTableModel.setData triggered on application exit; and while the value of the QComboBox, at this time, at 3rd row 2nd column was indeed '70', this got written into my table - as a string - without any intention, which I do not want.


    Note: since there is a call to setData from setModelData, I'd assume the setData is dependent on setModelData; however, even if the method setModelData is completely commented away from the code (with openPersistentEditor left), the same experiment prints out:

    setData self.sender()=None row=2 col=1 value='70'
    ... self._data=[[67, None, 41], [99, None, 5], [27, '70', 47]]


    ... so setData of the model gets independently triggered by application close (even without setModelData)
    ​

    Here is another example:


    • I start the application, I get the window rendered with the table


    • I click on empty space in the application window - I get a printout:

      setModelData self.sender()=None index.row()=2 index.column()=1 editor.currentText()='70'
      setData self.sender()=None row=2 col=1 value='70'
      ... self._data=[[67, None, 41], [99, None, 5], [27, '70', 47]]

    • I close the application - there is no printout.

    Another example:

    • I start the application, I get the window rendered with the table
    • I click on empty space in the application window - I get a printout:
      setModelData self.sender()=None index.row()=2 index.column()=1 editor.currentText()='70'
      setData self.sender()=None row=2 col=1 value='70'
      ... self._data=[[67, None, 41], [99, None, 5], [27, '70', 47]]
    • I click on the QComboBox at row=2 col=1 (third row, second column) - popup is shown, there is no printout
    • I click on the QComboBox at second row, second column - popup is shown, and exact same previous printout is repeated

    What causes these triggers of QStyledItemDelegate.setModelData and QAbstractTableModel.setData on application exit, click on empty space - and every time a QComboBox opens its popup in response to mouse left-click (though only if it is not the same QComboBox reported in the last printout); and how can I prevent them, so it is only the currentIndexChanged handler that is allowed to call setData?

    Here is the example:

    import sys
    import random
    from PyQt5.QtWidgets import QApplication, QTableView, QComboBox, QStyledItemDelegate
    from PyQt5.QtCore import Qt, QAbstractTableModel

    class RandomDataModel(QAbstractTableModel):
    def __init__(self, data, parent=None):
    super().__init__(parent)
    self._data = data

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

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

    def data(self, index, role=Qt.DisplayRole):
    if role == Qt.DisplayRole:
    return self._data[index.row()][index.column()]
    return None

    def setData(self, index, value, role=Qt.EditRole):
    row, col = index.row(), index.column()
    if role == Qt.EditRole:
    print(f"setData {self.sender()=} {row=} {col=} {value=}")
    self._data[row][col] = value
    self.dataChanged.emit(index, index)
    print(f"... {self._data=}")
    return True
    return False

    def flags(self, index):
    return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    class ComboBoxDelegate(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
    # self.parent() here is MainWindow (that is, QTableView)
    #print(f"createEditor {self.parent()=} {parent=}")
    combo = QComboBox(parent)
    combo.addItems([str(random.randint(1, 100)) for _ in range(3)])
    #combo.currentIndexChanged.connect(lambda idx, qmidx=index: self.commitComboValue(idx, qmidx))
    return combo

    def setModelData(self, editor, model, index):
    print(f"setModelData {self.sender()=} {index.row()=} {index.column()=} {editor.currentText()=}")
    model.setData(index, editor.currentText())

    def commitComboValue(self, index, qmidx):
    # self.parent() here is MainWindow (that is, QTableView)
    # self.sender() is QComboBox
    row, col = qmidx.row(), qmidx.column()
    value = int(self.sender().itemText(index))
    print(f"commitComboValue {row=} {col=} {index=} {value=} {self.sender()=}")
    table_view = self.parent()
    table_view.model().setData(qmidx, value)

    class MainWindow(QTableView):
    def __init__(self):
    super().__init__()

    data = [
    [random.randint(1, 100), None, random.randint(1, 100)],
    [random.randint(1, 100), None, random.randint(1, 100)],
    [random.randint(1, 100), None, random.randint(1, 100)],
    ]

    self.the_model = RandomDataModel(data)
    self.delegate = ComboBoxDelegate(self)

    self.setModel(self.the_model)
    self.setItemDelegateForColumn(1, self.delegate)

    for row in range(3):
    self.openPersistentEditor(self.the_model.index(row, 1))

    self.resize(300, 300)

    if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.setWindowTitle("QTableView Example")
    window.show()
    sys.exit(app.exec_())

    Continue reading...

Compartilhe esta Página