Search code examples
pythonpyqtpyqt4pyqt5qcombobox

How to make QCombobox expandable for hierarchical items?


i have a some dictionary variables such as :

{"Courses" : ["Math" , "Science" , "English"]} , 
{"Cars" : ["Ford" , "BMW" , "Mercedes"]} , etc.

i want to select values from qcombobox but firstly, i need to keep the cursor at keys from qcombobox then select the value from the highlighted key.

Courses
Cars-->Ford
    -->BMW
    -->Mercedes

How can i do something like that? Thanks for reply.


Solution

  • You can use a QPushButton with a QMenu as I show below:

    PyQt4:

    from PyQt4 import QtCore, QtGui
    
    data =[{
            "Courses": ["Math", "Science", "English"]
        },
        {
            "Cars": ["Ford", "BMW", "Mercedes"]
        }
    ]
    
    class ComboBoxExpandable(QtGui.QPushButton):
        currentTextChanged = QtCore.pyqtSignal(str)
        pathChanged = QtCore.pyqtSignal(list)
    
        def setData(self, value):
            menu = QtGui.QMenu(self)
            self.setMenu(menu)
            self.append_element(value, menu)
            menu.triggered.connect(self.on_triggered)
    
        @QtCore.pyqtSlot(QtGui.QAction)
        def on_triggered(self, action):
            self.setText(action.text())
            path = [action.text()] 
            w = action.parentWidget()
            while w.parentWidget() and isinstance(w.parentWidget(), QtGui.QMenu):
                path.insert(0, w.title())
                w = w.parentWidget()
            self.pathChanged.emit(path)
            self.currentTextChanged.emit(self.text())
    
        @staticmethod
        def append_element(value, menu):
            if isinstance(value, list):
                for e in value:
                    ComboBoxExpandable.append_element(e, menu)
            elif isinstance(value, dict):
                for k, v in value.items():
                    ComboBoxExpandable.append_element(v, menu.addMenu(k))
            else:
                menu.addAction(value)
    
    
    class Widget(QtGui.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
    
            combo = ComboBoxExpandable()
            combo.setData(data)
    
            result_label = QtGui.QLabel()
    
            combo.currentTextChanged.connect(result_label.setText)
            combo.pathChanged.connect(print)
    
            lay = QtGui.QFormLayout(self)
            lay.addRow("Select: ", combo)
            lay.addRow("Result: ", result_label)
    
    
    if __name__ == '__main__':
        import sys
    
        app = QtGui.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    

    PyQt5:

    from PyQt5 import QtCore, QtWidgets
    
    data =[{
            "Courses": ["Math", "Science", "English"]
        },
        {
            "Cars": ["Ford", "BMW", "Mercedes"]
        }
    ]
    
    class ComboBoxExpandable(QtWidgets.QPushButton):
        currentTextChanged = QtCore.pyqtSignal(str)
        pathChanged = QtCore.pyqtSignal(list)
    
        def setData(self, value):
            menu = QtWidgets.QMenu(self)
            self.setMenu(menu)
            self.append_element(value, menu)
            menu.triggered.connect(self.on_triggered)
    
        @QtCore.pyqtSlot(QtWidgets.QAction)
        def on_triggered(self, action):
            self.setText(action.text())
            path = [action.text()] 
            w = action.parentWidget()
            while w.parentWidget() and isinstance(w.parentWidget(), QtWidgets.QMenu):
                path.insert(0, w.title())
                w = w.parentWidget()
            self.pathChanged.emit(path)
            self.currentTextChanged.emit(self.text())
    
        @staticmethod
        def append_element(value, menu):
            if isinstance(value, list):
                for e in value:
                    ComboBoxExpandable.append_element(e, menu)
            elif isinstance(value, dict):
                for k, v in value.items():
                    ComboBoxExpandable.append_element(v, menu.addMenu(k))
            else:
                menu.addAction(value)
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
    
            combo = ComboBoxExpandable()
            combo.setData(data)
    
            result_label = QtWidgets.QLabel()
    
            combo.currentTextChanged.connect(result_label.setText)
            combo.pathChanged.connect(print)
    
            lay = QtWidgets.QFormLayout(self)
            lay.addRow("Select: ", combo)
            lay.addRow("Result: ", result_label)
    
    
    if __name__ == '__main__':
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    

    enter image description here