简体   繁体   中英

How should I connect CheckBox clicked signals in Table Widgets in PyQt5?

I would like to add various widgets to various cells in a Table Widget, and I would like to trigger commands when those widgets' values are changed. I can get the widgets into the table as desired, but I'm having problems connecting signals so that I know which widget has generated the signal.

Below is a simple example explaining the problem, using just checkboxes:

屏幕截图:TableWidget、CheckBoxes

from PyQt5 import QtWidgets, QtGui, QtCore


class Main(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        # create table:
        self.table = QtWidgets.QTableWidget()
        [self.table.insertRow(i) for i in [0,1,2]]
        [self.table.insertColumn(i) for i in [0,1]]
        # set values for first column:
        self.table.setItem(0, 0, QtWidgets.QTableWidgetItem('A') )
        self.table.setItem(1, 0, QtWidgets.QTableWidgetItem('B') )
        self.table.setItem(2, 0, QtWidgets.QTableWidgetItem('C') )
        # add checkboxes to second column:
        cb0  = QtWidgets.QCheckBox( parent=self.table )
        cb1  = QtWidgets.QCheckBox( parent=self.table )
        cb2  = QtWidgets.QCheckBox( parent=self.table )
        self.table.setCellWidget(0, 1, cb0)
        self.table.setCellWidget(1, 1, cb1)
        self.table.setCellWidget(2, 1, cb2)
        # connect table signals:
        self.table.cellChanged.connect(self.cell_changed)
        self.table.itemChanged.connect(self.item_changed)
        # connect checkbox signals:
        cb0.clicked.connect(self.checkbox_clicked)
        cb1.clicked.connect(self.checkbox_clicked)
        cb2.clicked.connect(self.checkbox_clicked)
        # show:
        self.setCentralWidget(self.table)
        self.setWindowTitle('TableWidget, CheckBoxes')
        self.show()

    def cell_changed(self, row, col):
        print(row, col)
    def checkbox_clicked(self, checked):
        print(checked)
    def item_changed(self, item):
        print(item)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    main = Main()
    app.exec_()

Based on table.cellChanged.connect I would naively expect a cellChanged signal when the checkboxes are changed. However this signal is not generated. Nor is the itemChanged signal. I can indeed see the clicked signals, but that is not very useful because it is unclear which checkbox has produced the signal.

One way to solve the problem is to create a different checkbox_clicked function for each checkbox, but that hardly seems elegant.

My questions are:

  1. Why is neither a cellChanged nor an itemChanged signal generated when a checkbox is changed?

  2. How should signals be connected in order to know which checkbox has generated the clicked signal?

  1. Why is neither a cellChanged nor an itemChanged signal generated when a checkbox is changed?

because when you use setCellWidget() a QTableWidgetItem is not created, and if we check the documentation of cellChanged and itemChanged :

void QTableWidget::cellChanged(int row, int column)

This signal is emitted whenever the data of the item in the cell specified by row and column has changed.

void QTableWidget::itemChanged(QTableWidgetItem *item)

This signal is emitted whenever the data of item has changed.


  1. How should signals be connected in order to know which checkbox has generated the clicked signal?

The way to obtain is indirectly, the first thing to know is that when the widget is added through the setCellWidget() method, the viewport() of the QTableWidget is set as a parent.

Also another thing that should be known is that the position of a widget that is accessed through pos() is relative to the parent, that is, in our case relative to viewport() .

There is a very useful method called sender() that returns the object that emits the signal, in this case it will return the QCheckBox .

As the position of the widget with respect to the viewport() is known, its QModelIndex is accessed through the indexAt() method, the QModelIndex has the information of the cell.

All of the above is implemented in the following example:

from PyQt5 import QtWidgets, QtGui, QtCore


class Main(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        # create table:
        self.table = QtWidgets.QTableWidget()
        self.table.setRowCount(3)
        self.table.setColumnCount(2)
        for i, letter in enumerate("ABC"):
            self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(letter))
        for i in range(self.table.rowCount()):
            ch = QtWidgets.QCheckBox(parent=self.table)
            ch.clicked.connect(self.onStateChanged)
            self.table.setCellWidget(i, 1, ch)
        self.setCentralWidget(self.table)
        self.setWindowTitle('TableWidget, CheckBoxes')
        self.show()

    def onStateChanged(self):
        ch = self.sender()
        print(ch.parent())
        ix = self.table.indexAt(ch.pos())
        print(ix.row(), ix.column(), ch.isChecked())


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    main = Main()
    sys.exit(app.exec_())

Another way to do it is through lambda methods or partial.functions where we pass directly new parameters.

from PyQt5 import QtWidgets, QtGui, QtCore


class Main(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        # create table:
        self.table = QtWidgets.QTableWidget()
        self.table.setRowCount(3)
        self.table.setColumnCount(2)
        for i, letter in enumerate("ABC"):
            self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(letter))
        for i in range(self.table.rowCount()):
            ch = QtWidgets.QCheckBox(parent=self.table)
            ch.clicked.connect(lambda checked, row=1, col=i: self.onStateChanged(checked, row, col))
            self.table.setCellWidget(i, 1, ch)
        self.setCentralWidget(self.table)
        self.setWindowTitle('TableWidget, CheckBoxes')
        self.show()

    def onStateChanged(self, checked, row, column):
        print(checked, row, column)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    main = Main()
    sys.exit(app.exec_())

If you want to know more information how to pass extra parameters through connect() you can review this answer .

use the stateChanged signal for checkboxes.

and my take about that code:

  • in some cases it's helpful to have a reference to checkbox widgets, for some logic actions.
  • use loops if possible
  • use explicit imports in PyQt - the class names are unique and it's more readable

for example:

from PyQt5.QtWidgets import QMainWindow, QTableWidgetItem, QCheckBox, QApplication
from typing import Dict

class Main(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        # create table:
        self.table = QTableWidget()
        self.table.insertColumn(0)
        self.table.insertColumn(1)
        self._items: Dict[QTableWidgetItem, QCheckBox] = {}
        for i, tag in enumerate(['A', 'B', 'C']):
            self.table.insertRow(i)
            item = QTableWidgetItem(tag)
            cb = QCheckBox(parent=self.table)
            self._items[item] = cb
            # set values for first column:
            self.table.setItem(i, 0, item)
            # add checkboxes to second column:
            self.table.setCellWidget(i, 1, cb)
            # connect cb signals:
            self._items[item].stateChanged.connect(self.checkbox_clicked)
        # connect table signals:
        self.table.cellChanged.connect(self.cell_changed)
        # show:
        self.setCentralWidget(self.table)
        self.setWindowTitle('TableWidget, CheckBoxes')
        self.show()

    def cell_changed(self, row, col):
        print(row, col)
    def checkbox_clicked(self, checked):
        print(checked)
    def item_changed(self, item):
        print(item)


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    main = Main()
    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