3

Based on https://stackoverflow.com/a/22775990/7894940 checkable combo box implementation, I want to go one step further and be able to display the list of the checked items directly on the main QComboBox label, i.e. when the displayed text of the QComboBox is not "unfolded".

So far, I am able to print the list of the checked items, but I don't have a clue how to change the main QComboBox label text with the former:

from PyQt5.QtWidgets import QApplication, QComboBox, QMainWindow, QWidget, QVBoxLayout
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtCore import Qt
import sys


class CheckableComboBox(QComboBox):
    def __init__(self):
        super(CheckableComboBox, self).__init__()
        self.view().pressed.connect(self.handle_item_pressed)
        self.setModel(QStandardItemModel(self))

    def handle_item_pressed(self, index):
        item = self.model().itemFromIndex(index)
        if item.checkState() == Qt.Checked:
            item.setCheckState(Qt.Unchecked)
            # print(item.text() + " was unselected.")
        else:
            item.setCheckState(Qt.Checked)
            # print(item.text() + " was selected.")
        self.check_items()

    def item_checked(self, index):
        item = self.model().item(index, 0)
        return item.checkState() == Qt.Checked

    def check_items(self):
        checkedItems = []
        for i in range(self.count()):
            if self.item_checked(i):
                checkedItems.append(self.model().item(i, 0).text())
        print(checkedItems)


class Dialog_01(QMainWindow):
    def __init__(self):
        super(QMainWindow, self).__init__()
        myQWidget = QWidget()
        myBoxLayout = QVBoxLayout()
        myQWidget.setLayout(myBoxLayout)
        self.setCentralWidget(myQWidget)
        self.ComboBox = CheckableComboBox()
        for i in range(3):
            self.ComboBox.addItem("Combobox Item " + str(i))
            item = self.ComboBox.model().item(i, 0)
            item.setCheckState(Qt.Unchecked)
        myBoxLayout.addWidget(self.ComboBox)

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

A picture explaining what I want: Diagram

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
rubebop
  • 392
  • 1
  • 7
  • 17
  • What do you mean with "So far, I am able to print the list of the checked items, but I don't have a clue how to change the main QComboBox label text with the former" exactly? Should the list order change in appearance when you check-mark the box from the dropdown menu? If easier exmapled you can draw a picture and add that to your question by updating it. – ZF007 Jan 16 '20 at 11:29
  • Thank you for your feed-back: I uploaded an explanatory picture. I hope it is a bit clearer now :) – rubebop Jan 16 '20 at 12:03
  • @rubebop I find it contradictory to mark the other question as correct, maybe I misunderstood your question. From what I understood is that the QComboBox label will show the texts of the checked items and that is what my solution does unlike the other solution that creates a new item every time you select or deselect an item, imagine that you have 1000 Sometimes then you would have many non-checkable options than the few checkable options. – eyllanesc Jan 16 '20 at 16:09
  • @ZF007 please don't add unnecessary tags, here python3 has no relevance – eyllanesc Jan 16 '20 at 16:12
  • @eyllanesc matter of copy/paste error. Its now fixed. I did add py3.x because of the print-statement construction showed its py3.x. No big deal. – ZF007 Jan 16 '20 at 17:05
  • This will [help](https://gis.stackexchange.com/questions/350148/qcombobox-multiple-selection-pyqt5?newreg=876bc02663c3424da0149e33752356b7) to solve your problem – Kumar Dec 13 '22 at 01:50

2 Answers2

6

You can override the paintEvent method:

class CheckableComboBox(QComboBox):
    def __init__(self):
        super(CheckableComboBox, self).__init__()
        self.view().pressed.connect(self.handle_item_pressed)
        self.setModel(QStandardItemModel(self))

    def handle_item_pressed(self, index):
        item = self.model().itemFromIndex(index)
        if item.checkState() == Qt.Checked:
            item.setCheckState(Qt.Unchecked)
        else:
            item.setCheckState(Qt.Checked)

    def item_checked(self, index):
        item = self.model().item(index, 0)
        return item.checkState() == Qt.Checked

    def check_items(self):
        checkedItems = []
        for i in range(self.count()):
            if self.item_checked(i):
                checkedItems.append(self.model().item(i, 0).text())
        return checkedItems

    def paintEvent(self, event):
        painter = QStylePainter(self)
        painter.setPen(self.palette().color(QPalette.Text))
        opt = QStyleOptionComboBox()
        self.initStyleOption(opt)
        opt.currentText = ",".join(self.check_items())
        painter.drawComplexControl(QStyle.CC_ComboBox, opt)
        painter.drawControl(QStyle.CE_ComboBoxLabel, opt)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • 1
    In the program I am implementing there is a big issue with this solution. I log the checkItems list, every time the check_items function is run. With your solution, that function already runs automatically by putting the cursor on the CheckableComboBox, which creates non desired logs. I don't want to run check_items if this wasn't triggered by the "pressed" action on the QPushButton via the handle_item_pressed function. – rubebop Jan 17 '20 at 08:50
1

With minor change to your original code you can implement the below code easily. When you open the comboBox tab you will see that all item labels are updated to current selected options.

Example:

  1. If all options are selected you will get this:

"Combobox Item 0 - selected item(s): 0, 1, 2"

or

  1. if for example option 0 was removed:

Combobox Item 2 - selected item(s): 1, 2

The code:

    def check_items(self):
        checkedItems = []
        for i in range(self.count()):
            if self.item_checked(i):
                checkedItems.append(i)

        self.update_labels(checkedItems)

    def update_labels(self, item_list):

        n = ''
        count = 0
        for i in item_list:
            if count == 0:
                n += ' %s' % i
            else:
                n += ', %s' % i

            count += 1

#        print('n : "%s".' % n)

        for i in range(self.count()):

            text_label = self.model().item(i, 0).text()

#            print('Current (index %s) text_label "%s"' % (i, text_label))
#            sys.stdout.flush()

            if text_label.find('-') >= 0:
                text_label = text_label.split('-')[0]

            item_new_text_label = text_label + ' - selected item(s): ' + n

#            self.model().item(i).setText(item_new_text_label)
             self.setItemText(i, item_new_text_label)  # copy/paste error corrected.

#        print(item_list)
        sys.stdout.flush()
rubebop
  • 392
  • 1
  • 7
  • 17
ZF007
  • 3,708
  • 8
  • 29
  • 48
  • I believe "text_label.rstrip(' %s' % i)" can be removed as the output is unused ;) – rubebop Jan 17 '20 at 09:29
  • You are right about that and can take it out without problem or use a # with an explanation note. It helps you to get the "CopyEditor" badge sooner ;-) – ZF007 Jan 17 '20 at 10:21
  • Thank you for the tip: I did remove that line ^^ – rubebop Jan 17 '20 at 10:32
  • @ZF007, the combo box item name also changed, I think, it's not a good one. May be user will get confused on the selection – Kumar Dec 13 '22 at 01:55