How to create combo-box QItemDelegate

3.9k views Asked by At

I implemented the following delegate, to provide a combobox in a QTableView. The use case is to replace a column (key) that is generally meaningless for the user (e.g. a numerical id) with a text equivalent.

The snippet below works (also for saving the proper value), but it has three issues:

  1. It displays the original value, instead of the text equivalent.
  2. A selection of rows in the QTableView provides all columns but not the one with this delegate.
  3. Ideally, I would like the combobox to appear as such, without the user having to click on it to find out it is one.

Note: The key can be any string (not necessarily an integer number). A typical example would be the country (the value "France" corresponds to the key "FR").

class ComboDelegate(QtGui.QItemDelegate):
    """
    A delegate that places a QComboBox in every
    cell of the column to which it is being applied
    """

    def __init__(self, parent, value_data):
        "Value_data is a list of tuples: (item, label)"
        QtGui.QItemDelegate.__init__(self, parent)
        self.value_data = value_data

    @property
    def items(self):
        "The list of items for display"
        return [item[0] for item in self.value_data]

    @property
    def labels(self):
        "The list of labels for display"
        return [item[1] for item in self.value_data]

    def item(self, label):
        "Get the item from a label"
        try:
            index = self.labels.index(label)
        except ValueError:
            pass
        print("Value no:  &%s" % index)
        return self.items[index]

    def createEditor(self, parent, option, index):
        "Create the editor (called each time)"

        combo = QtGui.QComboBox(parent)
        for duplet in self.value_data:
            # the duplet is label, item
            item, label = duplet
            combo.addItem(label)

        combo.currentIndexChanged.connect(self.currentIndexChanged)
        return combo

    def setEditorData(self, editor, index):
        editor.blockSignals(True)
        editor.setCurrentIndex(index.row())
        editor.blockSignals(False)

    def setModelData(self, editor, model, index):
        "This is the data stored into the field"
        print("Current text: %s" % editor.currentText())
        model.setData(index, self.item(editor.currentText()))

    def currentIndexChanged(self):
        self.commitData.emit(self.sender())
2

There are 2 answers

0
Kevin Krammer On

The first problem could most easily be solved by the model, i.e. it can provide the text for a certain value when asked for the DisplayRole and still provide the numerical value via EditRole.

For displaying the combobox there are two options

  1. when using a delegate, overwrite the paint() method to draw the combobox, e.g. by delegating to the current widget style just like QComboBox itself does

  2. instead of a delegate, set an index widget. see QAbstractItemView::setIndexWidget()

3
eyllanesc On

Add {your table view}.openPersistentEditor({your QModelIndex}) This is my solution:

import sys
from PySide import QtGui, QtCore


class ComboBoxDelegate(QtGui.QItemDelegate):
    def __init__(self, parent=None):
        super(ComboBoxDelegate, self).__init__(parent)
        self.items = []

    def setItems(self, items):
        self.items = items

    def createEditor(self, widget, option, index):
        editor = QtGui.QComboBox(widget)
        editor.addItems(self.items)
        return editor

    def setEditorData(self, editor, index):
        value = index.model().data(index, QtCore.Qt.EditRole)
        if value:
            editor.setCurrentIndex(int(value))

    def setModelData(self, editor, model, index):
        model.setData(index, editor.currentIndex(), QtCore.Qt.EditRole)

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

    def paint(self, painter, option, index):
        text = self.items[index.row()]
        option.text = text
        QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_ItemViewItem, option, painter)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    column = 0
    model = QtGui.QStandardItemModel(4, 2)
    tableview = QtGui.QTableView()
    tableview.setModel(model)
    delegate = ComboBoxDelegate()
    delegate.setItems([str(x) for x in range(10)])
    tableview.setItemDelegateForColumn(column, delegate)

    for row in range(4):
        for col in range(2):
            index = model.index(row, col, QtCore.QModelIndex())
            value = (row + 1)*(col + 1)
            model.setData(index, value)

    for i in range(model.rowCount()):
        tableview.openPersistentEditor(model.index(i, column))
    tableview.show()
    sys.exit(app.exec_())

enter image description here

enter image description here

String is placed, since I am in the previous example I am using str(), I show you another example showing countries.

import sys
from PySide import QtGui, QtCore


class ComboBoxDelegate(QtGui.QItemDelegate):
    def __init__(self, parent=None):
        super(ComboBoxDelegate, self).__init__(parent)
        self.items = []

    def setItems(self, items):
        self.items = items

    def createEditor(self, widget, option, index):
        editor = QtGui.QComboBox(widget)
        editor.addItems(self.items)
        return editor

    def setEditorData(self, editor, index):
        value = index.model().data(index, QtCore.Qt.EditRole)
        if value:
            editor.setCurrentIndex(int(value))

    def setModelData(self, editor, model, index):
        model.setData(index, editor.currentIndex(), QtCore.Qt.EditRole)

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

    def paint(self, painter, option, index):
        text = self.items[index.row()]
        option.text = text
        QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_ItemViewItem, option, painter)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    column = 0
    model = QtGui.QStandardItemModel(4, 2)
    tableview = QtGui.QTableView()
    tableview.setModel(model)
    delegate = ComboBoxDelegate()
    delegate.setItems([QtCore.QLocale.countryToString(QtCore.QLocale.Country(locale)) for locale in range(QtCore.QLocale.Afghanistan, QtCore.QLocale.Zulu+ 1 )])
    tableview.setItemDelegateForColumn(column, delegate)

    for row in range(4):
        for col in range(2):
            index = model.index(row, col, QtCore.QModelIndex())
            value = (row + 1)*(col + 1)
            model.setData(index, value)

    for i in range(model.rowCount()):
        tableview.openPersistentEditor(model.index(i, column))
    tableview.show()
    sys.exit(app.exec_())

enter image description here enter image description here