简体   繁体   English

我应该如何在 PyQt5 的 Table Widgets 中连接 CheckBox 单击信号?

[英]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.基于table.cellChanged.connect我天真地期望在复选框更改时会出现cellChanged信号。 However this signal is not generated.然而,不会产生该信号。 Nor is the itemChanged signal. itemChanged信号也不是。 I can indeed see the clicked signals, but that is not very useful because it is unclear which checkbox has produced the signal.我确实可以看到clicked信号,但这不是很有用,因为不清楚哪个复选框产生了信号。

One way to solve the problem is to create a different checkbox_clicked function for each checkbox, but that hardly seems elegant.解决该问题的一种方法是为每个复选框创建一个不同的checkbox_clicked函数,但这似乎不太优雅。

My questions are:我的问题是:

  1. Why is neither a cellChanged nor an itemChanged signal generated when a checkbox is changed?为什么更改复选框时既不生成cellChanged也不生成itemChanged信号?

  2. How should signals be connected in order to know which checkbox has generated the clicked signal?应该如何连接信号才能知道哪个复选框产生了clicked信号?

  1. Why is neither a cellChanged nor an itemChanged signal generated when a checkbox is changed?为什么更改复选框时既不生成 cellChanged 也不生成 itemChanged 信号?

because when you use setCellWidget() a QTableWidgetItem is not created, and if we check the documentation of cellChanged and itemChanged :因为当您使用setCellWidget()不会创建QTableWidgetItem ,如果我们检查cellChangeditemChanged的文档:

void QTableWidget::cellChanged(int row, int column) 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) void QTableWidget::itemChanged(QTableWidgetItem *item)

This signal is emitted whenever the data of item has changed.每当item的数据发生变化时,就会发出此信号。


  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.获取方式是间接的,首先要知道的是通过setCellWidget()方法添加widget时, QTableWidgetviewport()被设置为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() .另外应该知道的另一件事是,通过pos()访问的小部件的位置是相对于父级的,即在我们的例子中相对于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 .有一个非常有用的方法叫做sender() ,它返回发出信号的对象,在这种情况下它将返回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.由于小部件相对于viewport()是已知的,因此它的QModelIndex是通过indexAt()方法访问的, QModelIndex具有单元格的信息。

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.另一种方法是通过 lambda 方法或 partial.functions 直接传递新参数。

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 .如果您想了解更多有关如何通过 connect() 传递额外参数的信息,您可以查看此答案

use the stateChanged signal for checkboxes.对复选框使用stateChanged信号。

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在 PyQt 中使用显式导入 - 类名是唯一的并且更具可读性

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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