0

I'm implementing a delegate for a QTableView where two columns should be dropdowns where the user selects a value from an enum.

Below is an example:

from PyQt4 import QtGui, QtCore
import enum

Color = enum.Enum("Color", ("RED", "BLUE"))
Shape = enum.Enum("Shape", ("TRIANGLE", "CIRCLE"))


class EnumComboBoxDelegate(QtGui.QItemDelegate):
    def __init__(self, enum_cls, parent=None):
        super(EnumComboBoxDelegate, self).__init__(parent)
        self.enum_cls = enum_cls
        self.enum_objects = list(enum_cls)
        self.enum_names = [enum_obj.name for enum_obj in self.enum_objects]
        self.enum_values = [enum_obj.value for enum_obj in self.enum_objects]

    def createEditor(self, widget, option, index):
        editor = QtGui.QComboBox(widget)
        for user_friendly_name in self.enum_names:
            editor.addItem(user_friendly_name)
        return editor

    def setEditorData(self, editor, index):
        combobox_index, is_int = index.model().data(index, QtCore.Qt.EditRole).toInt()
        if is_int:
            editor.setCurrentIndex(combobox_index)
        else:
            editor.setCurrentIndex(0)

    def setModelData(self, editor, model, index):
        combobox_index = editor.currentIndex()
        if not combobox_index:
            combobox_index = 0

        model.setData(index, combobox_index, QtCore.Qt.EditRole)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)


class CentralWidget(QtGui.QWidget):
    def __init__(self, *args, **kwargs):
        super(CentralWidget, self).__init__(*args, **kwargs)

        main_layout = QtGui.QVBoxLayout()

        table_view = QtGui.QTableView()
        table_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        table_view.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)

        table_model = QtGui.QStandardItemModel(2, 2, None)
        table_view.setModel(table_model)

        color_combo_delegate = EnumComboBoxDelegate(Color)
        table_view.setItemDelegateForColumn(0, color_combo_delegate)

        shape_combo_delegate = EnumComboBoxDelegate(Shape)
        table_view.setItemDelegateForColumn(1, shape_combo_delegate)

        main_layout.addWidget(table_view)

        self.setLayout(main_layout)


def run_self_contained_widget():
    import sys

    app = QtGui.QApplication(sys.argv)
    main_window = QtGui.QMainWindow()
    main_window.setCentralWidget(CentralWidget())
    main_window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    run_self_contained_widget()

Now, if run, the code crashes on startup without traceback:

Process finished with exit code -1073741819 (0xC0000005)

If I change the delegates so they both use the same instance of EnumComboBoxDelegate, I get no errors:

color_combo_delegate = EnumComboBoxDelegate(Color)
table_view.setItemDelegateForColumn(0, color_combo_delegate)

shape_combo_delegate = EnumComboBoxDelegate(Shape)
table_view.setItemDelegateForColumn(1, color_combo_delegate)

What is causing this and how do I fix it? This code is using PyQt4 and Python 2.7, but it seems to be the same in Python 3.x

Kaia
  • 862
  • 5
  • 21
  • 1
    It's always good practice to ensure that delegates are kept alive. While a valid and common solution is to keep a reference as you suggested, a more appropriate (and Qt consistent) approach is to set the view as parent: you already have that argument in the subclass, just add it in the constructors. – musicamante Aug 22 '23 at 06:14

1 Answers1

0

I can offer a partial solution, but I haven't figured out exactly why. It appears that color_combo_delegate gets garbage-collected at the end of CentralWidget.__init__(). If you add the following to EnumComboBoxDelegate:

    def __del__(self):
        print("Oh no! I am about to be destroyed! Enum: {}".format(self.enum_cls.__name__))

You get the output

Oh no! I am about to be destroyed! Enum: Color
Process finished with exit code -1073741819 (0xC0000005)

The fixed code is:

self.color_combo_delegate = EnumComboBoxDelegate(Color)
table_view.setItemDelegateForColumn(0, self.color_combo_delegate)

self.shape_combo_delegate = EnumComboBoxDelegate(Shape)
table_view.setItemDelegateForColumn(1, self.shape_combo_delegate)

It's unclear to me why a ref to shape_combo_delegate exists and not one for color_combo_delegate, but this appears to fix the issue.

Kaia
  • 862
  • 5
  • 21