简体   繁体   中英

Dependent QComboBoxes in a QTableWidget

I am trying to link 3 different comboboxes in Pyqt5 inside a qtablewidget. the first 2 are linked in one slot and the third one is linked to the second combobox using a different slot in a separate definition. On the change of the first combobox, the 2nd combobox changes which makes the 3rd one change (similar to car selection websites).

The current issue is that on change of the first combobox the second signal also executes which makes it execute twice and giving me dictionary key error as the first execution doesnt include the key. The error occurs on Line 81 when trying to access the dictionary. See the image as it runs the index twice:

在此处输入图片说明

Attempt

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.m_tablewidget = QtWidgets.QTableWidget(0, 3)
        self.m_tablewidget.setHorizontalHeaderLabels(
            ["Col 1", "Col 2", "Col 3"]
        )
        self.m_button = QtWidgets.QPushButton("Add Row", clicked=self.onClicked)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QVBoxLayout(central_widget)
        lay.addWidget(self.m_tablewidget)
        lay.addWidget(self.m_button, alignment=QtCore.Qt.AlignLeft)
        self.vehicleHardware_dict = {'k': ['LiftSource_Wing_ChordConstantChordTapered', 'TPS_Active_Coolant'], 'X51': ['TPS_Passive_Tile', 'ThrustSource_Airbreathing_ScramjetTwoD']}
        self.procesVeh_dict = {'k': ['Aerodynamics', 'Optimization', 'Propulsion', 'Weight_Balance'], 'X51': ['Aerodynamics', 'Optimization', 'Propulsion', 'Weight_Balance']}
        self.processMethod_dict = {'Aerodynamics': ['kia', 'T1_case2_outNoIn'], 'Optimization': ['thomas'], 'Propulsion': ['test', 'rocket', 'T1_case3_InOutSame_inLess'], 'Weight_Balance': ['wing weight', 'T1_case1_inNoOut', 'T1_case3_inOutEq_inMore']}
        self.methodInput_dict = {'T1_case1_inNoOut': ['T'], 'T1_case2_outNoIn': [], 'T1_case3_InOutSame_inLess': ['T'], 'T1_case3_inOutEq_inMore': ['THRUST_REF'], 'kia': ['ACS', 'AEXIT', 'AE_AT', 'AIP', 'AISP', 'AISP_AVAIL_V', 'AISP_EFF', 'AISP_EFF_V', 'THETA2_N'], 'rocket': ['AIP', 'AISP', 'AISP_AVAIL_V', 'AISP_EFF', 'AISP_EFF_V', 'AISP_HW', 'AISP_REF'], 'test': ['ACS', 'Y_V'], 'thomas': ['ACS', 'AEXIT', 'AE_AT', 'AIP', 'AISP', 'AISP_AVAIL_V', 'CS', 'DIA_BODY'], 'wing weight': ['A', 'ABASE', 'ACAP', 'ACAP_SPLN', 'ACS', 'AEXIT', 'AE_AT']}

    @QtCore.pyqtSlot()
    def onClicked(self):
        combobox1_vehicle = QtWidgets.QComboBox()
        combobox2_hardware = QtWidgets.QComboBox()
        # combo_dummy = QtWidgets.QComboBox()
        for k, v in self.processMethod_dict.items():
            combobox1_vehicle.addItem(k, v)
            for kk, vv in self.methodInput_dict.items():
                combobox2_hardware.addItem(kk, vv)

        combobox3 = QtWidgets.QComboBox()
        combobox3.addItems(combobox2_hardware.currentData())

        combobox1_vehicle.currentIndexChanged.connect(self.onCurrentTextChanged1)
        combobox2_hardware.currentIndexChanged.connect(self.onCurrentTextChanged2)

        rc = self.m_tablewidget.rowCount()
        self.m_tablewidget.insertRow(rc)

        for i, combo in enumerate((combobox1_vehicle, combobox2_hardware, combobox3)):
            self.m_tablewidget.setCellWidget(rc, i, combo)

    @QtCore.pyqtSlot()
    def onCurrentTextChanged1(self):
        combobox1_vehicle = self.sender()
        if not isinstance(combobox1_vehicle, QtWidgets.QComboBox):
            return
        p = combobox1_vehicle.mapTo(self.m_tablewidget.viewport(), QtCore.QPoint())
        ix = self.m_tablewidget.indexAt(p)
        if not ix.isValid() or ix.column() != 0:
            return
        r = ix.row()
        data = combobox1_vehicle.currentData()
        combobox2_hardware = self.m_tablewidget.cellWidget(r, 1)
        if not isinstance(combobox2_hardware, QtWidgets.QComboBox):
            return
        combobox2_hardware.clear()
        combobox2_hardware.addItems(data)


    @QtCore.pyqtSlot()
    def onCurrentTextChanged2(self):
        combobox2_hardware = self.sender()
        if not isinstance(combobox2_hardware, QtWidgets.QComboBox):
            return
        p = combobox2_hardware.mapTo(self.m_tablewidget.viewport(), QtCore.QPoint())
        ix = self.m_tablewidget.indexAt(p)
        if not ix.isValid() or ix.column() != 1:
            return
        r = ix.row()
        # data = combobox2_hardware.currentData()
        valueOfKey = combobox2_hardware.currentText()
        print(combobox2_hardware)
        print(p)
        print(ix)

        data = self.methodInput_dict[valueOfKey]
        combobox3 = self.m_tablewidget.cellWidget(r, 2)
        if not isinstance(combobox3, QtWidgets.QComboBox):
            return
        combobox3.clear()
        if data == None:
            combobox3.addItem("")
        else:
            combobox3.addItems(data)



if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())

In cases where there is a lot of information it is necessary to look for a data structure that handles the information. In this case you could use a QAbstractItemModel to share all the QComboBox of the same row, and to change what is shown you should use the setRootModelIndex() method. In the following example I added a QTreeView so you can see how the model is distributed.

from PyQt5 import QtCore, QtGui, QtWidgets

d = {
    "Aerodynamics": {
        "kia": [
            "ACS",
            "AEXIT",
            "AE_AT",
            "AIP",
            "AISP",
            "AISP_AVAIL_V",
            "AISP_EFF",
            "AISP_EFF_V",
            "THETA2_N",
        ],
        "T1_case2_outNoIn": [],
    },
    "Optimization": {
        "thomas": [
            "ACS",
            "AEXIT",
            "AE_AT",
            "AIP",
            "AISP",
            "AISP_AVAIL_V",
            "CS",
            "DIA_BODY",
        ]
    },
    "Propulsion": {
        "test": ["ACS", "Y_V"],
        "rocket": [
            "AIP",
            "AISP",
            "AISP_AVAIL_V",
            "AISP_EFF",
            "AISP_EFF_V",
            "AISP_HW",
            "AISP_REF",
        ],
        "T1_case3_InOutSame_inLess": ["T"],
    },
    "Weight_Balance": {
        "wing weight": [
            "A",
            "ABASE",
            "ACAP",
            "ACAP_SPLN",
            "ACS",
            "AEXIT",
            "AE_AT",
        ],
        "T1_case1_inNoOut": ["T"],
        "T1_case3_inOutEq_inMore": ["THRUST_REF"],
    },
}


def dict_to_model(item, d):
    if isinstance(d, dict):
        for k, v in d.items():
            it = QtGui.QStandardItem(k)
            item.appendRow(it)
            dict_to_model(it, v)
    elif isinstance(d, list):
        for v in d:
            dict_to_model(item, v)
    else:
        item.appendRow(QtGui.QStandardItem(str(d)))


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.m_tablewidget = QtWidgets.QTableWidget(0, 3)
        self.m_tablewidget.setHorizontalHeaderLabels(
            ["Col 1", "Col 2", "Col 3"]
        )
        self.m_button = QtWidgets.QPushButton("Add Row", clicked=self.onClicked)
        self.m_treeview = QtWidgets.QTreeView()
        model = QtGui.QStandardItemModel(self)
        dict_to_model(model.invisibleRootItem(), d)
        self.m_treeview.setModel(model)
        self.m_treeview.expandAll()

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QVBoxLayout(central_widget)
        lay.addWidget(self.m_tablewidget)
        lay.addWidget(self.m_treeview)
        lay.addWidget(self.m_button, alignment=QtCore.Qt.AlignLeft)

    @QtCore.pyqtSlot()
    def onClicked(self):
        rc = self.m_tablewidget.rowCount()
        self.m_tablewidget.insertRow(rc)

        model = QtGui.QStandardItemModel(self)
        dict_to_model(model.invisibleRootItem(), d)

        it = model.invisibleRootItem()
        combos = []
        for i in range(3):
            combo = QtWidgets.QComboBox()
            combo.setModel(model)
            ix = model.indexFromItem(it)
            combo.setRootModelIndex(ix)
            combo.setCurrentIndex(0)
            it = it.child(0)
            self.m_tablewidget.setCellWidget(rc, i, combo)
            combos.append(combo)

        for combo in combos:
            combo.currentIndexChanged[int].connect(self.onCurrentIndexChanged)

    @QtCore.pyqtSlot(int)
    def onCurrentIndexChanged(self, index):
        combo = self.sender()
        if not isinstance(combo, QtWidgets.QComboBox):
            return
        p = combo.mapTo(self.m_tablewidget.viewport(), QtCore.QPoint())
        ix = self.m_tablewidget.indexAt(p)
        if not ix.isValid():
            return
        r, c = ix.row(), ix.column()
        if c == (self.m_tablewidget.columnCount() - 1):
            return
        model = combo.model()
        combo2 = self.m_tablewidget.cellWidget(r, c + 1)
        ix = combo.rootModelIndex()
        child_ix = model.index(index, 0, ix)
        combo2.setRootModelIndex(child_ix)
        combo2.setCurrentIndex(0)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM