You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5 QTableWidget嵌入控件时focusWidget().pos()始终返回0

问题描述

将组合了QCheckBox与QLineEdit的自定义QWidget通过setCellWidget()嵌入QTableWidget使用时,切换复选框状态、修改输入框文本,程序始终返回错误的单元格坐标(0,0):操作第3行(索引为2)的复选框时预期输出2 0,实际输出0 0;修改第3行输入框文本时,同样返回0行0列的单元格信息。
排查确认问题根源:QApplication.focusWidget().pos()始终返回坐标值0,导致QTableWidget.indexAt()方法定位到错误单元格。

问题复现代码
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, \
    QApplication, QTableWidgetItem, QLineEdit, QCheckBox
from PyQt5 import QtGui

class SimpleTable(QTableWidget):
    def __init__(self,window):
        QTableWidget.__init__(self)
        self.window = window
        

class myWidget(QWidget):
    def __init__(self,parent=None):
        super(myWidget,self).__init__()
        self.Layout1 = QHBoxLayout()
        self.item = QLineEdit("My custom text")
        
        self.Checkbox = QCheckBox()
        self.Checkbox.setCheckState(Qt.CheckState.Unchecked)

        self.Layout1.addWidget(self.Checkbox)
        self.Layout1.addWidget(self.item)

        self.item.home(True)
        
        self.setLayout(self.Layout1)
        
class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        layout = QHBoxLayout()
        self.setLayout(layout)
        
        self.table_widget = SimpleTable(window=self)
        layout.addWidget(self.table_widget)
        self.table_widget.setColumnCount(3)
        self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])

        items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
        for i in range(len(items)):
            c = QTableWidgetItem(items[i][0])
            m = QTableWidgetItem(items[i][1])
             
            self.table_widget.insertRow(self.table_widget.rowCount())
            self.table_widget.setItem(i, 1, c)
            self.table_widget.setItem(i, 2, m)

            myWidget1 = myWidget()
            myWidget1.Checkbox.stateChanged.connect(self.handleButtonClicked)
            myWidget1.item.editingFinished.connect(self.handle_cell_edited)      
            self.table_widget.setCellWidget(i,0,myWidget1)
            myWidget1.Layout1.setContentsMargins(50*i+10,0,0,0)

        self.show()

        self.table_widget.itemChanged.connect(self.handle_cell_edited)


    def handleButtonClicked(self):   
        button = QApplication.focusWidget()
        index = self.table_widget.indexAt(button.pos())
        if index.isValid():
            print(index.row(), index.column())
    
    def handle_cell_edited(self):
        if QApplication.focusWidget() != None:
            index = self.table_widget.indexAt(QApplication.focusWidget().pos())
            x,y = index.column(),index.row()
            if index.isValid():
                print("Handle Cell Edited",index.row(), index.column())
            if self.table_widget.item(y,x)!= None:
                print(f"Cell {x},{y} was changed to {self.table_widget.item(y,x).text()}.")

def main():
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec_())
main()
已尝试方案

Qt表格嵌入元素有两类常规实现,均无法完全满足需求:

  • 通过setItem()添加QTableWidgetItem:可设置Qt.ItemFlag.ItemIsUserCheckable标志实现单元格复选框,但无法自定义复选框缩进,也不能在同一单元格内同时组合复选框与可编辑文本框。
    该方案的实现代码如下:
    import sys
    from PyQt5.QtCore import Qt
    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, \
        QApplication, QTableWidgetItem, QLineEdit, QCheckBox
    from PyQt5 import QtGui
    
    class SimpleTable(QTableWidget):
        def __init__(self,window):
            QTableWidget.__init__(self)
            self.window = window
            
    class Window(QWidget):
        def __init__(self):
            super(Window, self).__init__()
            layout = QHBoxLayout()
            self.setLayout(layout)
            
            self.table_widget = SimpleTable(window=self)
            layout.addWidget(self.table_widget)
            self.table_widget.setColumnCount(3)
            self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
    
            items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
            for i in range(len(items)):
                c = QTableWidgetItem(items[i][0])
                m = QTableWidgetItem(items[i][1])
                 
                self.table_widget.insertRow(self.table_widget.rowCount())
                self.table_widget.setItem(i, 1, c)
                self.table_widget.setItem(i, 2, m)
                
                item = QTableWidgetItem("My Custom Text")
                item.setFlags(Qt.ItemFlag.ItemIsUserCheckable| Qt.ItemFlag.ItemIsEnabled)
                item.setCheckState(Qt.CheckState.Unchecked)
                self.table_widget.setItem(i,0,item)
    
            self.show()
            self.table_widget.itemChanged.connect(self.handle_cell_edited)
    
        def handleButtonClicked(self):    
            button = QApplication.focusWidget()
            index = self.table_widget.indexAt(button.pos())
            if index.isValid():
                print(index.row(), index.column())
        
        def handle_cell_edited(self):
            if QApplication.focusWidget() != None:
                index = self.table_widget.indexAt(QApplication.focusWidget().pos())
                x,y = index.column(),index.row()
                if index.isValid():
                    print("Handle Cell Edited",index.row(), index.column())
                if self.table_widget.item(y,x)!= None:
                    print(f"Cell {x},{y} was changed to {self.table_widget.item(y,x).text()}.")
    
    def main():
        app = QApplication(sys.argv)
        window = Window()
        sys.exit(app.exec_())
    main()
    
  • 通过自定义QItemDelegate实现灵活的单元格控件配置:该方案可满足所有自定义需求,但实现复杂度高,优先不考虑。
解决方法

错误原因

原代码直接调用子控件(QCheckBox/QLineEdit)的pos()方法获取坐标,该坐标是**相对于子控件直接父容器(自定义myWidget)**的局部坐标,永远在(0,0)附近,并非相对于QTableWidget视口的全局坐标,因此传给indexAt()会永远定位到(0,0)单元格。

最简实现(无需自定义委托)

不需要做坐标转换,直接在创建单元格控件时给控件实例绑定行列属性,槽函数中通过信号发送者(sender())找到对应控件实例,直接读取存储的行列值即可,无坐标计算误差:

  1. 首先修改myWidget类,增加行列属性存储:
class myWidget(QWidget):
    def __init__(self, row, col, parent=None):
        super(myWidget,self).__init__(parent)
        # 存储自身所在行列
        self.row = row
        self.col = col
        self.Layout1 = QHBoxLayout()
        self.item = QLineEdit("My custom text")
        
        self.Checkbox = QCheckBox()
        self.Checkbox.setCheckState(Qt.CheckState.Unchecked)

        self.Layout1.addWidget(self.Checkbox)
        self.Layout1.addWidget(self.item)
        self.item.home(True)
        # 消除控件周围默认边距,避免单元格显示错位
        self.Layout1.setContentsMargins(2,2,2,2)
        self.setLayout(self.Layout1)
  1. 创建控件实例时传入当前行列,修改槽函数逻辑,直接从控件实例读行列:
# 循环中创建控件的代码修改为
for i in range(len(items)):
    c = QTableWidgetItem(items[i][0])
    m = QTableWidgetItem(items[i][1])
     
    self.table_widget.insertRow(self.table_widget.rowCount())
    self.table_widget.setItem(i, 1, c)
    self.table_widget.setItem(i, 2, m)
    # 传入当前行列i,0
    myWidget1 = myWidget(i, 0)
    myWidget1.Checkbox.stateChanged.connect(self.handleButtonClicked)
    myWidget1.item.editingFinished.connect(self.handle_cell_edited)      
    self.table_widget.setCellWidget(i,0,myWidget1)
    myWidget1.Layout1.setContentsMargins(50*i+10,0,0,0)

# 槽函数修改为
def handleButtonClicked(self):
    # 拿到发信号的checkbox
    checkbox = self.sender()
    # 找到其父容器即myWidget实例
    cell_widget = checkbox.parent()
    while not isinstance(cell_widget, myWidget):
        cell_widget = cell_widget.parent()
    print(cell_widget.row, cell_widget.col)

def handle_cell_edited(self):
    sender = self.sender()
    # 判断是输入框发的信号还是表格item发的信号
    if isinstance(sender, QLineEdit):
        cell_widget = sender.parent()
        while not isinstance(cell_widget, myWidget):
            cell_widget = cell_widget.parent()
        row, col = cell_widget.row, cell_widget.col
        print("Handle Cell Edited", row, col)
        # 直接读取输入框文本即可,不需要额外读table item
        print(f"Cell {col},{row} was changed to {sender.text()}.")
    else:
        # 处理普通QTableWidgetItem的编辑信号
        item = self.table_widget.currentItem()
        if item:
            print("Handle Cell Edited", item.row(), item.column())
            print(f"Cell {item.column()},{item.row()} was changed to {item.text()}.")

如果表格后续有插入、删除行的操作,只需要遍历所有单元格的setCellWidget实例,更新它们存储的row属性即可,逻辑非常简单。


内容的提问来源于stack exchange,提问作者spamme180

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:42:58