Search code examples
pythonqtmodelpyqtqcombobox

How to restore the index of a QComboBox delegate in a QTableView?


There is a QTableView(), one of it's column is filled with QComboBoxes. The question is how to select item in combobox that is in QTableView() according to data taken from dictionary

I see that I should apply self.combo.setCurrentIndex(self.combo.findText( status_str)) but can't understand how to get that variable status_str in comboBox or place in code where to apply it. Also I cannot understand how make comboBox appear only after double clicking. If cell was not double clicked it must looks like any other cell.

The sample of code:

data = {"first":{"status":"closed"},"second":{"status":"expired"},"third":{ "status":"cancelled"}}
class ComboDelegate(QItemDelegate):
    def __init__(self, parent):
        QItemDelegate.__init__(self, parent)
    def paint(self, painter, option, index):
        self.combo = QComboBox(self.parent())
        li = []
        li.append("closed")
        li.append("expired")
        li.append("cancelled")
        li.append("waiting")
        self.combo.addItems(li)
        #self.combo.setCurrentIndex(self.combo.findText( status_str ))
        if not self.parent().indexWidget(index):
            self.parent().setIndexWidget(           index,          self.combo          )

class TableView(QTableView):
    def __init__(self, *args, **kwargs):
        QTableView.__init__(self, *args, **kwargs)
        self.setItemDelegateForColumn(1, ComboDelegate(self))

class MainFrame(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        table = TableView(self)
        self.model = QStandardItemModel()
        table.setModel(self.model)
        MainWindow = QVBoxLayout()
        MainWindow.addWidget(table)
        self.setLayout(MainWindow)
        self.fillModel()

    def fillModel(self):
        for i in data:
            print i
            name_str = i
            status_str = data[i]["status"]
            name = QStandardItem(name_str)
            status = QStandardItem(status_str)
            items = [name, status]
            self.model.appendRow(items)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = MainFrame()
    main.show()
    main.move(app.desktop().screen().rect().center() -     main.rect().center())
    sys.exit(app.exec_())

Solution

  • Overriding QItemDelegate.paint is not the recommended method for creating a delegate. QItemDelegate has methods such as createEditor and setEditorData which you should override instead. These methods are called appropriately by Qt.

    In createEditor you should create your comboBox, and return it. For example:

    def createEditor(self, parent, option, index):
        editor = QComboBox(parent)
        li = []
        li.append("closed")
        li.append("expired")
        li.append("cancelled")
        li.append("waiting")
        editor.addItems(li)
        return editor
    

    In setEditorData you query your model for the current index of the combobox. This will be called For example:

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

    Note that in this example, I've relied on the default implementation of QItemDelegate.setModelData() to save the current text of the combobox into the EditRole. If you want to do something more complex (for example saving the combobox index instead of the text), you can save/restore data to a different role (for example Qt.UserRole) in which case you would modify where you get the role in the setEditorData method as well as overriding setModelData like so:

    def setEditorData(self, editor, index):
        value = index.model().data(index, Qt.UserRole)
        editor.setCurrentIndex(int(value))
    
    def setModelData(self, editor, model, index):
        model.setData(index, editor.currentIndex(), Qt.UserRole)
    

    Here is a minimal working example of the above code! Note that I've turned off support for QVariant using sip so that the model returns native Python types.

    import sys
    import sip
    sip.setapi('QVariant', 2)
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
    data = {"first":{"status":"closed"},"second":{"status":"expired"},"third":{ "status":"cancelled"}}
    
    class ComboDelegate(QItemDelegate):
    
        def createEditor(self, parent, option, index):
            editor = QComboBox(parent)
            li = []
            li.append("closed")
            li.append("expired")
            li.append("cancelled")
            li.append("waiting")
            editor.addItems(li)
            return editor
    
        def setEditorData(self, editor, index):
            value = index.model().data(index, Qt.EditRole)
            editor.setCurrentIndex(editor.findText(value))
    
    
    class Example(QMainWindow):
    
        def __init__(self):
            super(Example, self).__init__()
    
            self.tableview = QTableView()
            self.tableview.setItemDelegateForColumn(1, ComboDelegate())
    
            self.setCentralWidget(self.tableview)
    
            self.model = QStandardItemModel()
            self.tableview.setModel(self.model)
            self.fillModel()
    
            self.show()
    
        def fillModel(self):
            for i in data:
                name_str = i
                status_str = data[i]["status"]
                name = QStandardItem(name_str)
                status = QStandardItem(status_str)
                items = [name, status]
                self.model.appendRow(items)
    
    
    if __name__ == '__main__':    
        app = QApplication(sys.argv)
        ex = Example()
        sys.exit(app.exec_())
    

    EDIT

    I've just noticed your other question about automatically showing the the comboBoxafter you double click. I have a hack for doing that to which I've used before. It relies on passing the view into the delegate and adding the following lines to the createEditor method:

            editor.activated.connect(lambda index, editor=editor: self._view.commitData(editor))
            editor.activated.connect(lambda index, editor=editor: self._view.closeEditor(editor,QAbstractItemDelegate.NoHint))
            QTimer.singleShot(10,editor.showPopup)
    

    Full working example:

    import sys
    import sip
    sip.setapi('QVariant', 2)
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    
    data = {"first":{"status":"closed"},"second":{"status":"expired"},"third":{ "status":"cancelled"}}
    
    class ComboDelegate(QItemDelegate):
        def __init__(self, view):
            QItemDelegate.__init__(self)
            self._view = view
    
    
        def createEditor(self, parent, option, index):
            editor = QComboBox(parent)
            li = []
            li.append("closed")
            li.append("expired")
            li.append("cancelled")
            li.append("waiting")
            editor.addItems(li)
    
    
            editor.activated.connect(lambda index, editor=editor: self._view.commitData(editor))
            editor.activated.connect(lambda index, editor=editor: self._view.closeEditor(editor,QAbstractItemDelegate.NoHint))
            QTimer.singleShot(10,editor.showPopup)
    
            return editor
    
        def setEditorData(self, editor, index):
            value = index.model().data(index, Qt.EditRole)
            editor.setCurrentIndex(editor.findText(value))
    
    
    class Example(QMainWindow):
    
        def __init__(self):
            super(Example, self).__init__()
    
            self.tableview = QTableView()
            self.tableview.setItemDelegateForColumn(1, ComboDelegate(self.tableview))
    
            self.setCentralWidget(self.tableview)
    
            self.model = QStandardItemModel()
            self.tableview.setModel(self.model)
            self.fillModel()
    
            self.show()
    
        def fillModel(self):
            for i in data:
                name_str = i
                status_str = data[i]["status"]
                name = QStandardItem(name_str)
                status = QStandardItem(status_str)
                items = [name, status]
                self.model.appendRow(items)
    
    
    if __name__ == '__main__':    
        app = QApplication(sys.argv)
        ex = Example()
        sys.exit(app.exec_())