PyQt5表格中所选的项并不包含所有已选中的项
我有一个 QTableWidget,想要选中任意数量的单元格,输入一个新值,然后在按下 ENTER 时,让该值出现在所有被选中的单元格中。然而,新值只在一个单元格中显示。下面是复现该错误所需的最小可行代码:
import pandas as pd
from PyQt5.QtWidgets import (QDialog, QDialogButtonBox, QApplication,
QVBoxLayout, QTableWidget, QTableWidgetItem)
from PyQt5.QtCore import Qt, pyqtSlot
import sys
class NewTable(QDialog):
def __init__(self, lnew, parent=None):
super().__init__(parent)
self.setWindowTitle("Enter Info")
self.resize(1280, 720)
self.lnew = lnew
self.dfnew = pd.DataFrame(data=lnew, columns=["NAME"])
self.dfnew.insert(1, "ADDRESS", None)
self.dfnew.insert(2, "CITY", None)
self.dfnew.insert(3, "STATE", None)
self.dfnew.insert(4, "ZIP", None)
self.result = None
vlayout = QVBoxLayout()
self.parmtbl = QTableWidget()
vlayout.addWidget(self.parmtbl)
self.parmtbl.setColumnCount(len(self.dfnew.columns))
self.parmtbl.setRowCount(len(self.dfnew.index))
self.parmtbl.setHorizontalHeaderLabels(self.dfnew.columns.tolist())
for index, row in self.dfnew.iterrows():
self.parmtbl.setItem(index, 0, QTableWidgetItem(row["NAME"]))
self.parmtbl.item(index, 0).setFlags(Qt.ItemIsSelectable)
self.parmtbl.cellChanged.connect(self.onmulticellchange)
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
vlayout.addWidget(self.buttonBox)
self.setLayout(vlayout)
@pyqtSlot(int, int)
def onmulticellchange(self, irow, icol): # TODO - Why is multi-cell editing not working?
self.parmtbl.blockSignals(True)
snewval = self.parmtbl.item(irow, icol).text()
rc = self.oncellchange(irow, icol, snewval)
print([x.row() for x in self.parmtbl.selectedItems()]) # Only 1 row in list
for eachitem in self.parmtbl.selectedItems():
print(self.dfnew["NAME"].iloc[eachitem.row()], rc) # If I highlight all cells in the "ADDRESS" column and type "Abbey Road", only "Ringo 1" is printed to the terminal
if eachitem.row() == irow and eachitem.column() == icol:
rc = 1
elif rc:
rc = self.oncellchange(eachitem.row(), eachitem.column(), snewval)
self.parmtbl.blockSignals(False)
def oncellchange(self, irow, icol, snewval):
self.dfnew.iloc[irow, icol] = snewval
self.parmtbl.item(irow, icol).setText(snewval)
return 1
if __name__ == '__main__':
lnew = ["John", "Paul", "George", "Ringo"]
app = QApplication(sys.argv)
ex = NewTable(lnew)
ex.show()
sys.exit(app.exec_())
在运行上述代码时,只有一个单元格会被修改。我添加了打印语句来查看 self.parmtbl.selectedItems() 的内容,尽管我选中了好几个单元格,但它只包含一个地址。
解决方案
解释
与QTableWidget中相关的 "item" 取值函数类似(例如 currentItem() 或 item()),QTableWidget只返回那些已经在表格上显式设置的QTableWidgetItem(使用 setItem()),或其数据要么是由用户(编辑单元格)设置,要么是通过表模型的编程方式设置的项(通过 setData())。
selectedItems() 的工作方式与之完全相同:它也只返回当前选区内“已经存在”的QTableWidgetItems,即使选区中还包含其他单元格。
在 __init__ 末尾添加以下几行试试看:
self.parmtbl.selectAll()
print('selectedItems()', len(self.parmtbl.selectedItems()))
print('selectedIndexes()', len(self.parmtbl.selectedIndexes()))
第一条 print() 将完全不会显示被选中的项,尽管所有启用的单元格都被选中,如第二条 print() 所示。
在你的情况中,当单元格编辑结束时,QTableWidget会因为将数据提交到模型而隐式为该单元格创建并设置一个QTableWidgetItem,但选区中的许多(如果不是全部的话)其他单元格此时显然是“空白”的,因此你最终只会得到那些在此之前被编辑或设置过的单元格所对应的项。 如果选区中的其他单元格并不对应实际的QTableWidgetItem,它们不会包含在该函数调用的结果中。
你还可以通过在表格中手动编辑几个单元格,然后选中“全部单元格”,最后在该选中区域内编辑一个条目来进一步演示:你会看到只有先前已经编辑过的单元格现在才会被正确更新。
记住:QTableWidget是 Qt模型/视图框架中的一个便捷类,基于QTableView(因此也继承自QAbstractItemView),并在内部实现了自己的“私有”模型,类似于基于QTreeView的 QTreeWidget和基于QListView的 QListWidget。 它适用于简单、较高层次的用法:当你需要对其内容和API进行更复杂的访问时,务必查阅它所使用或继承自的任何组件的文档。
解决方案
解决这个问题的正确办法是依赖底层类的实际选取集合,可以通过 selectedIndexes() 获取(这是对相关 QAbstractItemView 函数 的进一步覆盖),它始终返回当前选中模型中的所有QModelIndex。
在你的简单用例中,实际上不需要再改动其他东西,因为你正在访问的函数(row() 和 column())在命名和行为上对于QModelIndex与 QTableWidgetItem十分相似:
def onmulticellchange(self, irow, icol):
self.parmtbl.blockSignals(True)
snewval = self.parmtbl.item(irow, icol).text()
rc = self.oncellchange(irow, icol, snewval)
print([x.row() for x in self.parmtbl.selectedIndexes()])
for eachitem in self.parmtbl.selectedIndexes():
print(self.dfnew["NAME"].iloc[eachitem.row()], rc)
# etc.
注意,当访问一个 pandas 模型时,通常(也是推荐的)做法是使用带有QTableView的 QAbstractTableModel。它看起来比直接使用QTableWidget要复杂,但实际上通常更合适、效率更高、并且实现错误的概率更低。
就此问题多做研究,因为有大量相关资料,StackOverflow也不例外。