Search code examples
pythonqtpyqtqtableviewqcombobox

How to use QComboBox as delegate with QTableView


The code below creates a single QTableView. Double-clicking its item will set it with a delegated QComboBox.

Problem:
When the ComboBox is clicked its pull-down menu shows up momentary and then it collapses back to its unrolled state.

enter image description here

If the comboBox would be set to be editable using combox.setEditable(True), the pull-down menu would stay open as desired. But then the combobox's items become editable. And it is not what needed. Since the combobox's items should only be selectable.

How to fix the self-collapsing combobox's behavior?

P.s. I have noticed then when ComboBox is set to be editable and its pull-down menu is unrolled the model's data() is constantly being called... is self-collapsing behavior could probably triggered by model?

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class ComboDelegate(QItemDelegate):
    comboItems=['Combo_Zero', 'Combo_One','Combo_Two']
    def createEditor(self, parent, option, proxyModelIndex):
        combo = QComboBox(parent)
        combo.addItems(self.comboItems)
        # combo.setEditable(True)
        self.connect(combo, SIGNAL("currentIndexChanged(int)"), self, SLOT("currentIndexChanged()"))
        return combo

    def setModelData(self, combo, model, index):
        comboIndex=combo.currentIndex()
        text=self.comboItems[comboIndex]        
        model.setData(index, text)
        print '\t\t\t ...setModelData() 1', text

    @pyqtSlot()
    def currentIndexChanged(self): 
        self.commitData.emit(self.sender())

class MyModel(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items=['Data_Item01','Data_Item02','Data_Item03']

    def rowCount(self, parent=QModelIndex()):
        return len(self.items)
    def columnCount(self, parent=QModelIndex()):
        return 1

    def data(self, index, role):        
        if not index.isValid(): return QVariant()

        row=index.row()
        item=self.items[row]

        if row>len(self.items): return QVariant()

        if role == Qt.DisplayRole:
            print ' << >> MyModel.data() returning ...', item
            return QVariant(item) 

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

    def setData(self, index, text):
        self.items[index.row()]=text

if __name__ == '__main__':
    app = QApplication(sys.argv)

    model = MyModel()
    tableView = QTableView()
    tableView.setModel(model)

    delegate = ComboDelegate()

    tableView.setItemDelegate(delegate)
    tableView.resizeRowsToContents()

    tableView.show()
    sys.exit(app.exec_())

Solution

  • Your original code is working in PyQt5, the combobox stays open. But the user has to click to open the editor and then to click to open the combobox. To avoid this i replaced QComboBox by QlistWidget in your code. Additionally i set editorGeometry:

    import sys
    # from PyQt4.QtCore import *
    # from PyQt4.QtGui import *
    
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    
    class ComboDelegate(QItemDelegate):
        editorItems=['Combo_Zero', 'Combo_One','Combo_Two']
        height = 25
        width = 200
        def createEditor(self, parent, option, index):
            editor = QListWidget(parent)
            # editor.addItems(self.editorItems)
            # editor.setEditable(True)
            editor.currentItemChanged.connect(self.currentItemChanged)
            return editor
    
        def setEditorData(self,editor,index):
            z = 0
            for item in self.editorItems:
                ai = QListWidgetItem(item)
                editor.addItem(ai)
                if item == index.data():
                    editor.setCurrentItem(editor.item(z))
                z += 1
            editor.setGeometry(0,index.row()*self.height,self.width,self.height*len(self.editorItems))
    
        def setModelData(self, editor, model, index):
            editorIndex=editor.currentIndex()
            text=editor.currentItem().text() 
            model.setData(index, text)
            # print '\t\t\t ...setModelData() 1', text
    
        @pyqtSlot()
        def currentItemChanged(self): 
            self.commitData.emit(self.sender())
    
    class MyModel(QAbstractTableModel):
        def __init__(self, parent=None, *args):
            QAbstractTableModel.__init__(self, parent, *args)
            self.items=['Data_Item01','Data_Item02','Data_Item03']
    
        def rowCount(self, parent=QModelIndex()):
            return len(self.items)
        def columnCount(self, parent=QModelIndex()):
            return 1
    
        def data(self, index, role):        
            if not index.isValid(): return QVariant()
    
            row=index.row()
            item=self.items[row]
    
            if row>len(self.items): return QVariant()
    
            if role == Qt.DisplayRole:
                # print ' << >> MyModel.data() returning ...', item
                return QVariant(item) 
    
        def flags(self, index):
            return Qt.ItemIsEditable | Qt.ItemIsEnabled
    
        def setData(self, index, text):
            self.items[index.row()]=text
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        model = MyModel()
        tableView = QTableView()
        tableView.setModel(model)
    
        delegate = ComboDelegate()
    
        tableView.setItemDelegate(delegate)
        for i in range(0,tableView.model().rowCount()):
            tableView.setRowHeight(i,tableView.itemDelegate().height)
        for i in range(0,tableView.model().columnCount()):
            tableView.setColumnWidth(i,tableView.itemDelegate().width)
    
        tableView.show()
        sys.exit(app.exec_())