Search code examples
pythonpyqtpyqt5qtablewidgetqcombobox

How to link two comboboxes in a table of multiple comboboxes?


I am trying to link the value of combobox in column 1 with the box in column 3. On change of value in column 1 the values in the dropdown combo box need to change to a new set. On click of the button, a new row is to be added with similar combobox capability. The issue is that the only row that is responsive to the combobox value change in column 1 is in the most recent row added. How can i retain the link in all rows while adding rows as needed?

I have it currently set that the active row is the most current. How can I obtain the active row on click of the combobox inorder to activate that row? I am storing all combobxes in a dict.

My Attempt:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
        self.tableWidget.setGeometry(QtCore.QRect(60, 100, 551, 331))
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(3)
        self.tableWidget.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(2, item)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(60, 450, 75, 23))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        item = self.tableWidget.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "Col 1"))
        item = self.tableWidget.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "Col 2"))
        item = self.tableWidget.horizontalHeaderItem(2)
        item.setText(_translate("MainWindow", "Col 3"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))

class mainWindow(Ui_MainWindow, QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(mainWindow, self).__init__(parent)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.populate_table)

    # Combo box creator templates
    def comboBox_populator(self,values):
        combo = QtWidgets.QComboBox()
        for element in values:
            combo.addItem(element)
        return combo

    # Action to do on vehcile change
    def onComboChange(self):
        # selVehNames = self.selVehNames
        self.switchTo = self.comboBoxRows[self.counter][0].currentText()
        if self.switchTo == [] or self.switchTo == '':
            pass
        else:
            if self.switchTo == 'a':
                col1Data3 = ['x','y','z']
            elif self.switchTo == 'b':
                col1Data3 = ['4','5','6']
            elif self.switchTo == 'c':
                col1Data3 = ['21','22','23']
            else:
                pass
            # Update dict with comboboxes
            # col1Data1 = self.comboBoxRows[self.counter][0]
            # col1Data2 = self.comboBoxRows[self.counter][1]
            # self.AllComboBoxes = [col1Data1, col1Data2, col1Data3]

            colDataBox3 = self.comboBox_populator(col1Data3)
            print(col1Data3)
            # self.comboBoxRows.update({self.counter:self.AllComboBoxes})
            self.tableWidget.setCellWidget(self.counter,2,colDataBox3)

    # Populate the table
    def populate_table(self):
        self.AllComboBoxes = []
        self.comboBoxRows = {}

        # Table row information
        rowCountcur = self.tableWidget.currentRow()
        self.tableWidget.insertRow(rowCountcur+1)
        self.counter = rowCountcur + 1

        # General Data
        col1Data1 = ['a','b','c']
        col1Data2 = ['e','f','g']
        col1Data3 = ['x','y','z']

        # Create combobox
        colDataBox1 = self.comboBox_populator(col1Data1)
        colDataBox2 = self.comboBox_populator(col1Data2)
        colDataBox3 = self.comboBox_populator(col1Data3)
        self.AllComboBoxes = [colDataBox1, colDataBox2, colDataBox3]
        self.comboBoxRows.update({self.counter:self.AllComboBoxes})

        # Populate columns
        self.tableWidget.setCellWidget(self.counter,0,self.comboBoxRows[self.counter][0])
        # Populate vehicle list and cell, col 2
        self.tableWidget.setCellWidget(self.counter,1,self.comboBoxRows[self.counter][1])
        # Populate mission order list and cell, col 3
        self.tableWidget.setCellWidget(self.counter,2,self.comboBoxRows[self.counter][2])

        self.comboBoxRows[self.counter][0].currentTextChanged.connect(self.onComboChange)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = mainWindow()
    w.show()
    sys.exit(app.exec_())

Solution

  • The problem in your case is that self.counter never changes since currentRow does not indicate the row number and in your case it is always -1 so self.counter will always be 0.

    The key to the problem is to obtain the row of the QComboBox and to obtain it you must follow the following procedure:

    • Obtain combobox using sender() in the slot associated with the currentIndexChanged signal.

    • Obtain the position of the QComboBox topleft from the viewport() of the QTableWidget using mapTo() method.

    • Get row using the QModelIndex obtained by the indexAt() method.

    On the other hand you can use itemData to store the options of the other combobox with respect to the initial combobox.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    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)
    
        @QtCore.pyqtSlot()
        def onClicked(self):
            d = {
                "a": ["x", "y", "z"],
                "b": ["4", "5", "6"],
                "c": ["21", "22", "23"],
            }
            combobox1 = QtWidgets.QComboBox()
            for k, v in d.items():
                combobox1.addItem(k, v)
            combobox2 = QtWidgets.QComboBox()
            combobox2.addItems(combobox1.currentData())
    
            combobox3 = QtWidgets.QComboBox()
            combobox3.addItems(["e", "f", "g"])
    
            combobox1.currentIndexChanged.connect(self.onCurrentTextChanged)
    
            rc = self.m_tablewidget.rowCount()
            self.m_tablewidget.insertRow(rc)
    
            for i, combo in enumerate((combobox1, combobox2, combobox3)):
                self.m_tablewidget.setCellWidget(rc, i, combo)
    
        @QtCore.pyqtSlot()
        def onCurrentTextChanged(self):
            combobox1 = self.sender()
            if not isinstance(combobox1, QtWidgets.QComboBox):
                return
            p = combobox1.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.currentData()
            combobox3 = self.m_tablewidget.cellWidget(r, 2)
            if not isinstance(combobox3, QtWidgets.QComboBox):
                return
            combobox3.clear()
            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_())