【问题标题】:How should I connect CheckBox clicked signals in Table Widgets in PyQt5?我应该如何在 PyQt5 的表格小部件中连接 CheckBox 点击信号?
【发布时间】:2023-03-10 17:03:01
【问题描述】:

我想将各种小部件添加到表格小部件中的各种单元格,并且我想在这些小部件的值发生更改时触发命令。我可以根据需要将小部件放入表中,但在连接信号时遇到问题,因此我知道是哪个小部件生成了信号。

下面是一个解释问题的简单示例,仅使用复选框:

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

基于table.cellChanged.connect,当复选框更改时,我会天真地期待cellChanged 信号。然而,不会产生这个信号。 itemChanged 信号也不是。我确实可以看到clicked 信号,但这不是很有用,因为不清楚哪个复选框产生了信号。

解决问题的一种方法是为每个复选框创建一个不同的checkbox_clicked 函数,但这似乎并不优雅。

我的问题是:

  1. 为什么在更改复选框时既不生成cellChanged 也不生成itemChanged 信号?

  2. 应该如何连接信号才能知道哪个复选框生成了clicked 信号?

【问题讨论】:

    标签: python python-3.x pyqt pyqt5 qtablewidget


    【解决方案1】:
    1. 为什么既不生成 cellChanged 也不生成 itemChanged 信号时 复选框已更改?

    因为当您使用setCellWidget() 时,不会创建QTableWidgetItem,如果我们查看cellChangeditemChanged 的文档:

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

    只要行和列指定的单元格中item的数据发生变化,就会发出此信号。

    void QTableWidget::itemChanged(QTableWidgetItem *item)

    每当 item 的数据发生变化时,都会发出此信号。


    1. 应该如何连接信号才能知道哪个复选框产生了点击信号?

    获取的方式是间接的,首先要知道的是,通过setCellWidget()方法添加widget时,QTableWidgetviewport()被设置为parent。

    另外应该知道的是,通过pos() 访问的小部件的位置是相对于父级的,即在我们的例子中是相对于viewport()

    有一个非常有用的方法叫做sender(),它返回发出信号的对象,在这种情况下它将返回QCheckBox

    由于widget相对于viewport()的位置已知,其QModelIndex通过indexAt()方法访问,QModelIndex具有单元格的信息。

    以上所有内容都在以下示例中实现:

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

    另一种方法是通过 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_())
    

    如果您想了解更多关于如何通过 connect() 传递额外参数的信息,您可以查看this answer

    【讨论】:

    • 我确实阅读了文档,但我对以下陈述感到非常困惑:“...每当 item... 的数据发生变化时。”似乎添加的小部件也是一个项目。如果它说“……每当 QTableWidgetItem 的数据发生变化时……就会更清楚。对于使用 setCellWidget 方法添加了其他小部件的单元格不会发出信号”。
    • 一个项目不同于一个小部件,当它在文档中引用一个项目时,它被理解为QTableWidgetItem。 :P
    • @user2269232 问题是有很多小部件可以添加到 QTableWidget 中,并且每个小部件都有自己的信号和事件,因此制作映射所有这些字段的通用代码是不可能的,而是QTableWidgetItem 是与 QTableWidget 一起使用的自定义类,因此方法和信号是已知的。
    • 现在完全清楚了,谢谢!对于我只需要行和列的简单情况,我更喜欢您建议的 lambda 方法。我可以看到 sender() 方法通常非常有用。再次感谢!!
    【解决方案2】:

    为复选框使用stateChanged 信号。

    以及我对该代码的看法:

    • 在某些情况下,对于某些逻辑操作,参考复选框小部件会很有帮助。
    • 尽可能使用循环
    • 在 PyQt 中使用显式导入 - 类名是唯一的,并且更具可读性

    例如:

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

    【讨论】:

      猜你喜欢
      • 2020-02-19
      • 2016-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-08
      • 2021-12-28
      • 1970-01-01
      相关资源
      最近更新 更多