首页 > 解决方案 > QTableWidget 中的依赖 QComboBoxes

问题描述

我正在尝试在 qtablewidget 中链接 Pyqt5 中的 3 个不同的组合框。前 2 个链接在一个插槽中,第三个链接到第二个组合框,使用单独定义中的不同插槽。在第一个组合框发生变化时,第二个组合框发生变化,这使得第三个组合框发生变化(类似于选车网站)。

当前的问题是,在更改第一个组合框时,第二个信号也会执行,这使其执行两次并给我字典键错误,因为第一次执行不包括键。尝试访问字典时,错误发生在第 81 行。查看图像,因为它运行索引两次:

在此处输入图像描述

试图

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_())

标签: pythonpyqtpyqt5

解决方案


在有大量信息的情况下,有必要寻找处理信息的数据结构。在这种情况下,您可以使用 aQAbstractItemModel来共享同一行的所有 QComboBox,并更改显示的内容,您应该使用该setRootModelIndex()方法。在下面的示例中,我添加了一个QTreeView,以便您可以看到模型是如何分布的。

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_())

推荐阅读