Reputation: 233
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_())
Upvotes: 1
Views: 253
Reputation: 243887
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_())
Upvotes: 2