pythonqtpyqtqcombobox

PyQt: How to set Combobox Items be Checkable?


To keep the GUI widgets number to minimum I need to find a way to give to user a choice of pull-down menu items that could be used to filter out the displayed in a listWidget items. Let's say the listWidget lists 5 different categories of Items: "Cat A", "Cat B","Cat C","Cat D","Cat E". I could implement the radio or checkboxes for each item category. But then 5 radio buttons or checkboxes would take a lot of GUI space. A combobox with the checkable items seems to be a right choice. Any ideas?

from PyQt4 import QtGui, QtCore
import sys, os


class CheckableComboBox(QtGui.QComboBox):
    def __init__(self):    
        super(CheckableComboBox, self).__init__()

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


class Dialog_01(QtGui.QMainWindow):
    def __init__(self):
        super(QtGui.QMainWindow,self).__init__()

        myQWidget = QtGui.QWidget()
        myBoxLayout = QtGui.QVBoxLayout()
        myQWidget.setLayout(myBoxLayout)
        self.setCentralWidget(myQWidget)

        self.ComboBox = CheckableComboBox()
        for i in range(3):
            self.ComboBox.addItem("Combobox Item " + str(i))

        myBoxLayout.addWidget(self.ComboBox)


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    dialog_1 = Dialog_01()
    dialog_1.show()
    dialog_1.resize(480,320)
    sys.exit(app.exec_())

Solution

  • This idea of a multi-select combo has come up before, but I'm not sure that its the best solution. Really, all that's needed is a tool-button with a drop-down menu (similar to the history buttons in a web-browser).

    Here's a basic demo that illustrates both options (button left, combo right):

    screenshot screenshot

    PyQt5:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class CheckableComboBox(QtWidgets.QComboBox):
        def __init__(self, title = '', parent=None):
            super().__init__(parent)
            self.setTitle(title)
            self.view().pressed.connect(self.handleItemPressed)
            self.setModel(QtGui.QStandardItemModel(self))
    
        def handleItemPressed(self, index):
            item = self.model().itemFromIndex(index)
            if item.checkState() == QtCore.Qt.Checked:
                item.setCheckState(QtCore.Qt.Unchecked)
            else:
                item.setCheckState(QtCore.Qt.Checked)
    
        def title(self):
            return self._title
    
        def setTitle(self, title):
            self._title = title
            self.repaint()
    
        def paintEvent(self, event):
            painter = QtWidgets.QStylePainter(self)
            painter.setPen(self.palette().color(QtGui.QPalette.Text))
            opt = QtWidgets.QStyleOptionComboBox()
            self.initStyleOption(opt)
            opt.currentText = self._title
            painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt)
            painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
    
    class Dialog(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            hbox = QtWidgets.QHBoxLayout()
            self.comboBox = CheckableComboBox('Categories', self)
            self.toolButton = QtWidgets.QToolButton(self)
            self.toolButton.setText('Categories ')
            self.toolMenu = QtWidgets.QMenu(self)
            for index in range(3):
                self.comboBox.addItem(f'Category {index}')
                item = self.comboBox.model().item(index, 0)
                item.setCheckState(QtCore.Qt.Unchecked)
                action = self.toolMenu.addAction(f'Category {index}')
                action.setCheckable(True)
            self.toolButton.setMenu(self.toolMenu)
            self.toolButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
            hbox.addWidget(self.toolButton)
            hbox.addWidget(self.comboBox)
            layout = QtWidgets.QVBoxLayout(self)
            layout.addLayout(hbox)
            layout.addStretch()
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(['Test'])
        dialog = Dialog()
        dialog.show()
        app.exec()
            
    

    PyQt4:

    from PyQt4 import QtCore, QtGui
    
    class CheckableComboBox(QtGui.QComboBox):
        def __init__(self, title = '', parent=None):
            super().__init__(parent)
            self.setTitle(title)
            self.view().pressed.connect(self.handleItemPressed)
            self.setModel(QtGui.QStandardItemModel(self))
    
        def handleItemPressed(self, index):
            item = self.model().itemFromIndex(index)
            if item.checkState() == QtCore.Qt.Checked:
                item.setCheckState(QtCore.Qt.Unchecked)
            else:
                item.setCheckState(QtCore.Qt.Checked)
    
        def title(self):
            return self._title
    
        def setTitle(self, title):
            self._title = title
            self.repaint()
    
        def paintEvent(self, event):
            painter = QtGui.QStylePainter(self)
            painter.setPen(self.palette().color(QtGui.QPalette.Text))
            opt = QtGui.QStyleOptionComboBox()
            self.initStyleOption(opt)
            opt.currentText = self._title
            painter.drawComplexControl(QtGui.QStyle.CC_ComboBox, opt)
            painter.drawControl(QtGui.QStyle.CE_ComboBoxLabel, opt)
    
    class Dialog(QtGui.QWidget):
        def __init__(self):
            super().__init__()
            hbox = QtGui.QHBoxLayout()
            self.comboBox = CheckableComboBox('Categories', self)
            self.toolButton = QtGui.QToolButton(self)
            self.toolButton.setText('Categories ')
            self.toolMenu = QtGui.QMenu(self)
            for index in range(3):
                self.comboBox.addItem('Category %s' % index)
                item = self.comboBox.model().item(index, 0)
                item.setCheckState(QtCore.Qt.Unchecked)
                action = self.toolMenu.addAction('Category %s' % index)
                action.setCheckable(True)
            self.toolButton.setMenu(self.toolMenu)
            self.toolButton.setPopupMode(QtGui.QToolButton.InstantPopup)
            hbox.addWidget(self.toolButton)
            hbox.addWidget(self.comboBox)
            layout = QtGui.QVBoxLayout(self)
            layout.addLayout(hbox)
            layout.addStretch()
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(['Test'])
        dialog = Dialog()
        dialog.show()
        app.exec()