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())找到对应控件实例,直接读取存储的行列值即可,无坐标计算误差:
- 首先修改
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)
- 创建控件实例时传入当前行列,修改槽函数逻辑,直接从控件实例读行列:
# 循环中创建控件的代码修改为 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
相关产品推荐
相关产品推荐

