pythonmacosqwidgetqcomboboxpyqt6

PyQt6: The CheckableCombobox never hides


I'm trying to implement a code capable to create a checkable combobox with PyQt6. My problem is that when I open the combobox for the first time, it stays opened and never hides. The functions seem to work well but I guess when it shows up for the first time it has a bug that I'm not able to fix. Please someone help me. The code I'm using is:

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QComboBox
from PyQt6.QtCore import Qt, QEvent
from PyQt6.QtGui import QStandardItem

class CheckableComboBox(QComboBox):
    def __init__(self, parent=None):
        super(CheckableComboBox,self).__init__(parent)
        self.setEditable(True)
        #self.lineEdit().setReadOnly(True)

        self.model().dataChanged.connect(self.updateLineEditField)

    def addItems(self, items, itemList=None):
        itemList = [] if itemList is None else itemList
        for indx, text in enumerate(items):
            try:
                data = itemList[indx]
            except(IndexError):
                data = None
            self.addItem(text,data)
            

    def addItem(self,text,userData=None):
        item = QStandardItem()
        item.setText(text)
        if not userData is None:
            item.setData(userData)
        
        item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable)
        item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
        self.model().appendRow(item)
    
    def updateLineEditField(self):
        text_container = []
        for i in range(self.model().rowCount()):
            if self.model().item(i).checkState() == Qt.CheckState.Checked:
                text_container.append(self.model().item(i).text())

        text_string=', '.join(text_container)
        self.lineEdit().setText(text_string)

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.window_width, self.window_height = 1200,800
        self.setMinimumSize(self.window_width, self.window_height)
        self.setStyleSheet('''
            QWidget {
                font-size: 10px;
            }
        ''')

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        combobox = CheckableComboBox()
        combobox.addItems(colors)
        self.layout.addWidget(combobox)

    def closeEvent(self, event):
        event.accept()

if __name__ == '__main__':
    colors=["Blue", "Yellow", "Green", "Red"]
    app = QApplication(sys.argv)
    myApp = MyApp()
    myApp.show()

    try:
        sys.exit(app.exec())
    except SystemExit:
        print("Closing window...")

I just expect it to hide when it's opened and I click the box. Just for you to know, i'm using a Mac and the python version 3.9.6 64-bit. I don't know if this is significant but just in case. I've tried functions like eventFilter but it doesn't work for me neither.

#def eventFilter(self, widget, event: QEvent) -> bool:
    #    if widget == self.lineEdit():
    #        if event.type() == QEvent.Type.MouseButtonPress:
    #            if self.closeOnLineEditClick:
    #                print("hide")
    #                self.hidePopup()
    #            else:
    #                print("show")
    #                self.showPopup()
    #            return True
    #        return super().eventFilter(widget, event)
#
    #    if widget == self.view().viewport():
    #        if event.type() == QEvent.Type.MouseButtonRelease:
    #            indx = self.view().indexAt(event.pos())
    #            item = self.model().item(indx.row())
#
    #            if item.checkState() == Qt.CheckState.Checked:
    #                item.setCheckState(Qt.CheckState.Unchecked)
    #            else:
    #                item.setCheckState(Qt.CheckState.Checked)
    #            return True
    #        return super().eventFilter(widget, event)

As @musicamante asked, here you have the complete code when I use eventFilter() but for me it fixes nothing:

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QComboBox
from PyQt6.QtCore import Qt, QEvent
from PyQt6.QtGui import QStandardItem

class CheckableComboBox(QComboBox):
    def __init__(self, parent=None):
        super(CheckableComboBox,self).__init__(parent)
        self.setEditable(True)
        #self.lineEdit().setReadOnly(True)
        self.closeOnLineEditClick = False
        self.lineEdit().installEventFilter(self)
        self.model().dataChanged.connect(self.updateLineEditField)
    
    def eventFilter(self, widget, event: QEvent) -> bool:
        if widget == self.lineEdit():
            if event.type() == QEvent.Type.MouseButtonPress:
                if self.closeOnLineEditClick:
                    print("hide")
                    self.hidePopup()
                else:
                    print("show")
                    self.showPopup()
                    self.closeOnLineEditClick = True
                return True
        return super().eventFilter(widget, event)

    def addItems(self, items, itemList=None):
        itemList = [] if itemList is None else itemList
        for indx, text in enumerate(items):
            try:
                data = itemList[indx]
            except(IndexError):
                data = None
            self.addItem(text,data)
            

    def addItem(self,text,userData=None):
        item = QStandardItem()
        item.setText(text)
        if not userData is None:
            item.setData(userData)
        
        item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable)
        item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
        self.model().appendRow(item)
    
    def updateLineEditField(self):
        text_container = []
        for i in range(self.model().rowCount()):
            if self.model().item(i).checkState() == Qt.CheckState.Checked:
                text_container.append(self.model().item(i).text())

        text_string=', '.join(text_container)
        self.lineEdit().setText(text_string)

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.window_width, self.window_height = 1200,800
        self.setMinimumSize(self.window_width, self.window_height)
        self.setStyleSheet('''
            QWidget {
                font-size: 10px;
            }
        ''')

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        combobox = CheckableComboBox()
        combobox.addItems(colors)
        self.layout.addWidget(combobox)

    def closeEvent(self, event):
        event.accept()

if __name__ == '__main__':
    colors=["Blue", "Yellow", "Green", "Red"]
    app = QApplication(sys.argv)
    myApp = MyApp()
    myApp.show()

    try:
        sys.exit(app.exec())
    except SystemExit:
        print("Closing window...")

Solution

  • The code implementation exposed works. It seems the problem is caused by an specific Qt bug to macOS. Didn't find how to fix it. Just answered because the code is correct and you can use it in other operating systems. If someone someday finds out what bug I'm talking about and how to fix it, answer whenever! Thanks.

    import sys
    from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QComboBox
    from PyQt6.QtCore import Qt, QEvent
    from PyQt6.QtGui import QStandardItem
    
    class CheckableComboBox(QComboBox):
        def __init__(self, parent=None):
            super(CheckableComboBox,self).__init__(parent)
            self.setEditable(True)
            #self.lineEdit().setReadOnly(True)
            self.closeOnLineEditClick = False
            self.lineEdit().installEventFilter(self)
            self.model().dataChanged.connect(self.updateLineEditField)
        
        def eventFilter(self, widget, event: QEvent) -> bool:
            if widget == self.lineEdit():
                if event.type() == QEvent.Type.MouseButtonPress:
                    if self.closeOnLineEditClick:
                        print("hide")
                        self.hidePopup()
                    else:
                        print("show")
                        self.showPopup()
                        self.closeOnLineEditClick = True
                    return True
            return super().eventFilter(widget, event)
    
        def addItems(self, items, itemList=None):
            itemList = [] if itemList is None else itemList
            for indx, text in enumerate(items):
                try:
                    data = itemList[indx]
                except(IndexError):
                    data = None
                self.addItem(text,data)
                
    
        def addItem(self,text,userData=None):
            item = QStandardItem()
            item.setText(text)
            if not userData is None:
                item.setData(userData)
            
            item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable)
            item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
            self.model().appendRow(item)
        
        def updateLineEditField(self):
            text_container = []
            for i in range(self.model().rowCount()):
                if self.model().item(i).checkState() == Qt.CheckState.Checked:
                    text_container.append(self.model().item(i).text())
    
            text_string=', '.join(text_container)
            self.lineEdit().setText(text_string)
    
    class MyApp(QWidget):
        def __init__(self):
            super().__init__()
            self.window_width, self.window_height = 1200,800
            self.setMinimumSize(self.window_width, self.window_height)
            self.setStyleSheet('''
                QWidget {
                    font-size: 10px;
                }
            ''')
    
            self.layout = QVBoxLayout()
            self.setLayout(self.layout)
    
            combobox = CheckableComboBox()
            combobox.addItems(colors)
            self.layout.addWidget(combobox)
    
        def closeEvent(self, event):
            event.accept()
    
    if __name__ == '__main__':
        colors=["Blue", "Yellow", "Green", "Red"]
        app = QApplication(sys.argv)
        myApp = MyApp()
        myApp.show()
    
        try:
            sys.exit(app.exec())
        except SystemExit:
            print("Closing window...")