3

For example, the first combo box with option A and option B. The second combo box contains option 1,2,3,4,5 if I select option A in 1st combo box. The second combo box contains option a,b,c,d,e if I select option B in 1st combo box.

How could I do this in PyQt5? I have searched Qcombobox class, there is 2 class activated and currentIndexChanged, but do not know which one I need to use and how to use it in PyQt5.

Kester
  • 283
  • 2
  • 5
  • 10

2 Answers2

3

This is how I would do it:

import sys
from PyQt5 import QtWidgets


class ComboWidget(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super(ComboWidget, self).__init__(parent)
        self.setGeometry(50, 50, 200, 200)

        layout = QtWidgets.QVBoxLayout(self)

        self.comboA = QtWidgets.QComboBox()
        # The second addItem parameter is itemData, which we will retrieve later
        self.comboA.addItem('A', ['1', '2', '3', '4'])
        self.comboA.addItem('B', ['a', 'b', 'c', 'd', 'e'])
        self.comboA.currentIndexChanged.connect(self.indexChanged)
        layout.addWidget(self.comboA)

        self.comboB = QtWidgets.QComboBox()
        # We could have added the 1,2,3,4 items directly, but the following
        # is a bit more generic in case the combobox starts with a different
        # index for some reason:
        self.indexChanged(self.comboA.currentIndex())
        layout.addWidget(self.comboB)

        self.show()

    def indexChanged(self, index):
        self.comboB.clear()
        data = self.comboA.itemData(index)
        if data is not None:
            self.comboB.addItems(data)


def main():
    app = QtWidgets.QApplication(sys.argv)
    w = ComboWidget()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

That way, your code is a bit more future-proof, as it would allow you to add / modify / remove the items in any of the dropdowns quite easily if you wanted to do so at some point.

FernAndr
  • 1,448
  • 1
  • 14
  • 26
  • 2
    I see that you're aiming for a more generic solution - but employing user-visible text as keys is problematic, since it is prone to breakage if you ever need to support other languages. Qt already provides an API for directly associating items with data: [QComboBox.itemData](https://doc.qt.io/qt-5/qcombobox.html#itemData). So this can be used to either store the list of values themselves, or, even better, a function for generating them dynamically. – ekhumoro Aug 23 '17 at 12:56
  • @ekhumoro, Hi, do you mean to use "QComboBox.setitemdata" to set the data first and use "QComboBox.itemData" for retrieving the data? – Kester Aug 23 '17 at 13:18
  • @Kester. Yes, that was the idea. Or, more conveniently, add the data via the *`userdata`* argument of `addItem`, and retrieve it via `currentData`. – ekhumoro Aug 23 '17 at 13:23
  • 1
    @ekhumoro I have updated my answer to use itemData. Hopefully now it is in line with what you were suggesting (although for this simple example I am not generating that list through a function). I initially thought as a dictionary as the most straightforward way to store the data, but I have to admit that using itemData looks cleaner – FernAndr Aug 23 '17 at 14:56
0

I got it working by using 3 comboboxes (lets call them x, y and z)

  1. x contains A, B and is used to choose what options appear on the other combobox. Selecting A shows combobox y and selecting B shows combobox z

  2. y contains 1,2,3,4

  3. Z contains a,b,c,d

I used a QStackedLayout in place of the second combobox, loaded it with two comboboxes (y and z) and showed one of them at a time depending on which option is selected in combobox x.

The method comboOptionChanged handles this. This method called whenever a different option in combobox x is chosen.

import sys
from PyQt5.QtWidgets import QWidget, QComboBox, QApplication, QFormLayout, QLabel,QStackedLayout

class DynamicComboExample(QWidget):
    def __init__(self):
        super().__init__()
        self.setupUI()

    def setupUI(self):
        self.setGeometry(300,300, 500, 300)
        self.show()

        combo_a = QComboBox(self)
        combo_a.addItem('A')
        combo_a.addItem('B')

        # set slot for when option of combobox A is changed
        combo_a.currentIndexChanged[int].connect(self.comboOptionChanged)

        self.combo_b = QComboBox()
        self.combo_b.addItem('1')
        self.combo_b.addItem('2')
        self.combo_b.addItem('3')
        self.combo_b.addItem('4')

        self.combo_c = QComboBox()
        self.combo_c.addItem('a')
        self.combo_c.addItem('b')
        self.combo_c.addItem('c')
        self.combo_c.addItem('d')
        self.combo_c.addItem('e')

        # use a stacked layout to view only one of two combo box at a time
        self.combo_container_layout = QStackedLayout()

        self.combo_container_layout.addWidget(self.combo_b)
        self.combo_container_layout.addWidget(self.combo_c)

        combo_container = QWidget()
        combo_container.setLayout(self.combo_container_layout)

        form_layout = QFormLayout()

        form_layout.addRow(QLabel('A:\t'), combo_a)

        # the stacked layout is placed in place of the (meant to be) second combobox
        form_layout.addRow(QLabel('B:\t'), combo_container)


        self.setLayout(form_layout)


    def comboOptionChanged(self, idx):
        ''' gets called when option for combobox A is changed'''

        # check which combobox_a option is selected ad show the appropriate combobox in stacked layout
        self.combo_container_layout.setCurrentIndex(0 if idx == 0 else 1)


def main():
    app= QApplication(sys.argv)
    w = DynamicComboExample()
    exit(app.exec_())

if __name__ == '__main__':
    main()
Anonta
  • 2,500
  • 2
  • 15
  • 25
  • Thanks for help. Is it necessary to use two combo boxes to solve this problem? – Kester Aug 22 '17 at 16:48
  • Yes, only way of using one combobox to get the same result would be to remove all current options and then add all options of the new chosen combobox every time the user chooses a different option in the first combobox. Which is something you should avoid. – Anonta Aug 22 '17 at 16:53
  • Why is having comboboxes with dynamic data something to be avoided? Repopulating the items of a QComboBox is not a highly expensive operation after all, especially with a low amount of items – FernAndr Aug 22 '17 at 18:29
  • I am also interested in knowing why as I am actually populating comboboxes from a database each time the user clicks on them. – Saelyth Aug 24 '17 at 05:21
  • I simply consider it a bad design. Two different types of options should be kept in different places (even the widgets that show them). This can also render the gui unresponsive when there are a lot of items to load. @Saelyth if you'd like to repopulate the combobox every time it's better to keep the items in a list first and load items from there instead of connecting to the DB every time. – Anonta Aug 24 '17 at 05:34