0

As the title suggests I'm looking to get a combobox in a QTableView. I've looked at several other questions that deal with comboboxes in tableviews, but they mostly concern comboboxes as editors and that is not what I'm looking for.

I would like the combobox to be visible at all times and get its data from the underlying model. It doesn't have to set data in the model.

I tried to adapt this example of a progress bar delegate: How to include a column of progress bars within a QTableView? Leading to this code:

class ComboDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        combo = QStyleOptionComboBox()
        combo.rect = option.rect
        combo.currentText = 'hallo'  # just for testing
        combo.editable = True
        combo.frame = False
        QApplication.style().drawComplexControl(QStyle.CC_ComboBox, combo, painter)

But this gives me a greyed out, non functional combobox.

enter image description here

How do you get a functional combobox there?

mahkitah
  • 562
  • 1
  • 6
  • 19

1 Answers1

1

As the name suggests, the paint() function only draws a combobox, it does not create one.

If you want a persistent widget set for an item, and that widget doesn't need to update the model, then you should use setIndexWidget().

A basic implementation on a static model could be like this:

class SomeWidget(QWidget):
    def __init__(self):
        # ...
        self.table.setModel(someModel)
        for row in range(someModel.rowCount()):
            combo = QComboBox(editable=True)
            combo.addItem('hallo')
            self.table.setIndexWidget(someModel.index(row, 2), combo)

If the model can change at runtime (and is possibly empty at startup), then you need to connect to the rowsInserted signal:

class SomeWidget(QWidget):
    def __init__(self):
        # ...
        self.updateCombos()
        someModel.rowsInserted.connect(self.updateCombos)

    def updateCombos(self):
        for row in range(self.table.model().rowCount()):
            index = someModel.index(row, 2)
            if self.table.indexWidget(index):
                continue
            combo = QComboBox(editable=True)
            combo.addItem('hallo')
            self.table.setIndexWidget(index, combo)

Then you can access any combo based on the index row:

    def getComboValue(self, row):
        index = self.table.model().index(row, 2)
        widget = self.table.indexWidget(index)
        if isinstance(widget, QComboBox):
            return widget.currentText()

Remember: whenever you're studying the documentation, you must also review the documentation of all inherited classes. In this case you should not only read the docs about QTableView, but also the whole inheritance tree: QTableView > QAbstractItemView > QAbstractScrollArea > QFrame > QWidget > QObject and QPaintDevice.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • @musicamente. Thanks for your elaborate answer. This should help me a long way. You're right that paint() suggests only drawing, but the 'editable' and 'currentText' properties of QStyleOptionComboBox made me think that it was possible to get a functioning combobox that way. When do those properties get used? – mahkitah Jan 15 '22 at 09:43
  • @mahkitah A [QStyleOption](https://doc.qt.io/qt-5/qstyleoption.html#details) is an object that "contain[s] all the information that QStyle functions need to draw a graphical element". Standard Qt widgets don't usually draw themself on their own, but "ask" the current QStyle to draw in a consistent way, and style functions use the style option to know some specific widget features: long story short, in your case you were telling the style to *draw* a combobox that *looks* editable and with a text displayed as the current. You can read more on https://doc.qt.io/qt-5/style-reference.html – musicamante Jan 15 '22 at 10:33