[英]How to change row order in a QTableWidget by editing cells?
There are different ways to change row order in QTableWidget:在 QTableWidget 中有不同的方法来改变行顺序:
It turned out that these two approaches are not very practical for longer lists and my special purpose.事实证明,这两种方法对于较长的列表和我的特殊目的不是很实用。 So, I tried to implement the following approach by assigning the new position by changing cell values:
因此,我尝试通过更改单元格值来分配新的 position 来实现以下方法:
Example: position numbers 1,2,3,4,5
.示例: position 编号
1,2,3,4,5
。 If I change the value in row3,column1 from 3 to 1, the position numbers in the first column should change as follows:如果我将 row3,column1 中的值从 3 更改为 1,则第一列中的 position 数字应更改如下:
1 --> 2
2 --> 3
3 --> 1
4 --> 4
5 --> 5
However, it seems I get problems with setEditTriggers(QAbstractItemView.NoEditTriggers)
and setEditTriggers(QAbstractItemView.DoubleClicked)
.但是,似乎我遇到了
setEditTriggers(QAbstractItemView.NoEditTriggers)
和setEditTriggers(QAbstractItemView.DoubleClicked)
的问题。
Depending on some different code variations I tried, it looks like I still get an EditTrigger
although I think I have disabled EditTriggers via self.setEditTriggers(QAbstractItemView.NoEditTriggers)
.根据我尝试的一些不同的代码变体,看起来我仍然得到一个
EditTrigger
,尽管我认为我已经通过self.setEditTriggers(QAbstractItemView.NoEditTriggers)
禁用了 EditTriggers。 Or I get RecursionError: maximum recursion depth exceeded while calling a Python object
.或者我得到
RecursionError: maximum recursion depth exceeded while calling a Python object
。 Or TypeError: '>' not supported between instances of 'NoneType' and 'int'
.或
TypeError: '>' not supported between instances of 'NoneType' and 'int'
。
I hope I could make the problem clear enough.我希望我能把问题说清楚。 What am I doing wrong here?
我在这里做错了什么?
Code: (minimized non-working example. Should be copy & paste & run)代码:(最小化的非工作示例。应该是复制&粘贴&运行)
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QAction, QTableWidget, QTableWidgetItem, QVBoxLayout, QPushButton, QAbstractItemView
from PyQt5.QtCore import pyqtSlot, Qt
import random
class MyTableWidget(QTableWidget):
def __init__(self):
super().__init__()
self.setColumnCount(3)
self.setRowCount(7)
self.setSortingEnabled(False)
header = self.horizontalHeader()
header.setSortIndicatorShown(True)
header.sortIndicatorChanged.connect(self.sortItems)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.col_pos = 0
self.oldPosValue = None
self.manualChange = False
self.cellDoubleClicked.connect(self.cell_doubleClicked)
self.cellChanged.connect(self.cell_changed)
def cell_doubleClicked(self):
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
if self.currentColumn() != self.col_pos: # editing allowed only for this column
return
self.setEditTriggers(QAbstractItemView.DoubleClicked)
try:
self.oldPosValue = int(self.currentItem().text())
except:
pass
self.manualChange = True
def cell_changed(self):
if not self.manualChange:
return
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
try:
newPosValue = int(self.currentItem().text())
except:
newPosValue = None
rowChanged = self.currentRow()
print("Value: {} --> {}".format(self.oldPosValue, newPosValue))
if newPosValue>0 and newPosValue<=self.rowCount():
for row in range(self.rowCount()):
if row != rowChanged:
try:
value = int(self.item(row,self.col_pos).text())
if value<newPosValue:
self.item(row,self.col_pos).setData(Qt.EditRole,value+1)
except:
print("Error")
pass
else:
self.item(rowChanged,self.col_pos).setData(Qt.EditRole,self.oldPosValue)
print("New value outside range")
self.manualChange = True
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 table'
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(0,0,400,300)
self.layout = QVBoxLayout()
self.tw = MyTableWidget()
self.layout.addWidget(self.tw)
self.pb_refill = QPushButton("Refill")
self.pb_refill.clicked.connect(self.on_click_pb_refill)
self.layout.addWidget(self.pb_refill)
self.setLayout(self.layout)
self.show()
@pyqtSlot()
def on_click_pb_refill(self):
self.tw.setEditTriggers(QAbstractItemView.NoEditTriggers)
for row in range(self.tw.rowCount()):
for col in range(self.tw.columnCount()):
if col==0:
number = row+1
else:
number = random.randint(1000,9999)
twi = QTableWidgetItem()
self.tw.setItem(row, col, twi)
self.tw.item(row, col).setData(Qt.EditRole,number)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Result:结果:
The main problem is that you're trying to disable editing in the wrong way: toggling the edit triggers won't give you a valid result due to the way the view reacts to events.主要问题是您试图以错误的方式禁用编辑:由于视图对事件的反应方式,切换编辑触发器不会给您一个有效的结果。
The recursion error is due to the fact that you are changing data in the signal that reacts to data changes, which clearly is not a good thing to do.递归错误是由于您正在更改对数据更改做出反应的信号中的数据,这显然不是一件好事。
The other problem is related to the current item, which could become None
in certain situations.另一个问题与当前项目有关,在某些情况下可能变为
None
。
First of all, the correct way to disable editing of items is by setting the item's flags .首先,禁用项目编辑的正确方法是设置项目的标志。 This solves another problem you didn't probably found yet: pressing Tab while in editing mode, allows to change data in the other columns.
这解决了您可能还没有发现的另一个问题:在编辑模式下按Tab可以更改其他列中的数据。
Then, in order to correctly use the first column to set the order, you should ensure that all other rows get correctly "renumbered".然后,为了正确使用第一列来设置顺序,您应该确保所有其他行都正确“重新编号”。 Since doing that also requires setting data in other items, you must temporarily disconnect from the changed signal.
由于这样做还需要在其他项目中设置数据,因此您必须暂时断开与更改的信号的连接。
class MyTableWidget(QTableWidget):
def __init__(self):
super().__init__()
self.setColumnCount(3)
self.setRowCount(7)
self.setSortingEnabled(False)
header = self.horizontalHeader()
header.setSortIndicatorShown(True)
header.sortIndicatorChanged.connect(self.sortItems)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.itemChanged.connect(self.cell_changed)
def cell_changed(self, item):
if item.column():
return
newRow = item.data(Qt.DisplayRole)
self.itemChanged.disconnect(self.cell_changed)
if not 1 <= newRow <= self.rowCount():
if newRow < 1:
newRow = 1
item.setData(Qt.DisplayRole, 1)
elif newRow > self.rowCount():
newRow = self.rowCount()
item.setData(Qt.DisplayRole, self.rowCount())
otherItems = []
for row in range(self.rowCount()):
otherItem = self.item(row, 0)
if otherItem == item:
continue
otherItems.append(otherItem)
otherItems.sort(key=lambda i: i.data(Qt.DisplayRole))
for r, item in enumerate(otherItems, 1):
if r >= newRow:
r += 1
item.setData(Qt.DisplayRole, r)
self.itemChanged.connect(self.cell_changed)
def setItem(self, row, column, item):
# override that automatically disables editing if the item is not on the
# first column of the table
self.itemChanged.disconnect(self.cell_changed)
super().setItem(row, column, item)
if column:
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
self.itemChanged.connect(self.cell_changed)
Note that you must also change the function that creates the items and use item.setData
before adding the item to the table:请注意,您还必须更改创建项目的 function 并在将项目添加到表之前使用
item.setData
:
def on_click_pb_refill(self):
for row in range(self.tw.rowCount()):
for col in range(self.tw.columnCount()):
if col==0:
number = row+1
else:
number = random.randint(1000,9999)
twi = QTableWidgetItem()
twi.setData(Qt.EditRole, number)
self.tw.setItem(row, col, twi)
You can use slightly modified QStandardItemModel
and QSortFilterProxyModel
for that您可以为此使用稍作修改的
QStandardItemModel
和QSortFilterProxyModel
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt, pyqtSignal
import random
from contextlib import suppress
def shiftRows(old, new, count):
items = list(range(1, count + 1))
item = items.pop(old - 1)
items.insert(new - 1, item)
return {item: i + 1 for i, item in enumerate(items)}
class Model(QtGui.QStandardItemModel):
orderChanged = pyqtSignal()
def __init__(self, rows, columns, parent = None):
super().__init__(rows, columns, parent)
self._moving = True
for row in range(self.rowCount()):
self.setData(self.index(row, 0), int(row + 1))
self.setData(self.index(row, 1), random.randint(1000,9999))
self.setData(self.index(row, 2), random.randint(1000,9999))
self._moving = False
def swapRows(self, old, new):
self._moving = True
d = shiftRows(old, new, self.rowCount())
for row in range(self.rowCount()):
index = self.index(row, 0)
v = index.data()
if d[v] != v:
self.setData(index, d[v])
self.orderChanged.emit()
self._moving = False
def flags(self, index):
if index.column() == 0:
return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
def headerData(self, section, orientation, role):
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return self.index(section, 0).data()
return super().headerData(section, orientation, role)
def setData(self, index, value, role = Qt.DisplayRole):
if role == Qt.EditRole and index.column() == 0:
if self._moving:
return super().setData(self, index, value, role)
with suppress(ValueError):
value = int(value)
if value < 1 or value > self.rowCount():
return False
prev = index.data()
self.swapRows(prev, value)
return True
return super().setData(index, value, role)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
model = Model(5, 3)
sortModel = QtCore.QSortFilterProxyModel()
sortModel.setSourceModel(model)
model.orderChanged.connect(lambda: sortModel.sort(0))
view = QtWidgets.QTableView()
view.setModel(sortModel)
view.show()
app.exec_()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.