【发布时间】:2017-01-12 03:07:06
【问题描述】:
我有一个 2 级 QTreeWidget。顶层只有一列,仅用于对第二层进行分组,其中包含实际的多列数据。
当我通过单击标题(或任何其他方式,真的)进行排序时,我只希望对第二级进行排序,因为顶级项目在应用程序的其他地方设置了固定的顺序,不应该是受影响。
我如何做到这一点?
【问题讨论】:
标签: python sorting pyqt pyqt4 qtreewidget
我有一个 2 级 QTreeWidget。顶层只有一列,仅用于对第二层进行分组,其中包含实际的多列数据。
当我通过单击标题(或任何其他方式,真的)进行排序时,我只希望对第二级进行排序,因为顶级项目在应用程序的其他地方设置了固定的顺序,不应该是受影响。
我如何做到这一点?
【问题讨论】:
标签: python sorting pyqt pyqt4 qtreewidget
最简单的解决方案是创建QTreeWidgetItem 的子类并重新实现其__lt__ 方法,以便它始终返回False。 Qt 使用稳定的排序算法,因此这意味着项目将始终保持其原始顺序。此子类应仅用于顶级项目。
这是一个似乎符合您的规范的工作演示:
import sys
from random import randint
from PyQt4 import QtCore, QtGui
class TreeWidgetItem(QtGui.QTreeWidgetItem):
def __lt__(self, other):
return False
class Window(QtGui.QTreeWidget):
def __init__(self):
super(Window, self).__init__()
self.setHeaderLabels('Name X Y'.split())
for text in 'One Two Three Four'.split():
parent = TreeWidgetItem([text])
for text in 'Red Blue Green Yellow'.split():
child = QtGui.QTreeWidgetItem([
text, str(randint(0, 9)), str(randint(0, 9))])
parent.addChild(child)
self.addTopLevelItem(parent)
self.expandAll()
self.setSortingEnabled(True)
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(800, 100, 320, 400)
window.show()
sys.exit(app.exec_())
【讨论】:
您可以像这样使用 QTreeWidgetItem(s) 的 sortChildren 方法:
sort_column = 1
# Iterate top-level elements
for index in range(tree.topLevelItemCount()):
# Obtain the actual top-level item
top_level_item = tree.topLevelItem(index)
# Sort
top_level_item.sortChildren(sort_column)
请注意,如果您将新子项添加到项目中,则需要再次对其进行排序(仅更改的项目)
编辑
谢谢,这是朝着正确方向迈出的一步,但现在的问题是如何覆盖默认的排序行为。我正在做 self.treeWidget.header().sortIndicatorChanged.connect(self.sortChanged),但由于这是一个信号,它只是在小部件进行常规排序后触发,所以最后它没有效果 - 顶部在调用函数之前对级别项目进行排序
除非您选择自定义模型/视图,否则您可以尝试两种方法(我没有测试过):
1.覆盖QTreeWidget的“排序”方法
尝试用一些无操作替换sortItems() 方法,但小部件可能有其他内部调用的私有方法。
2。覆盖根项目sortChildren()
排序可能是调用(隐藏的)根项 sortChildren() 来实现的,它将对顶级项进行排序并调用它们的 sortChildren()。您可能能够弄清楚如何使用rootIndex()/setRootIndex() 获取/设置此项,然后覆盖排序方法(例如root.sortChildren = lambda c, o: None)。
【讨论】:
self.treeWidget.header().sortIndicatorChanged.connect(self.sortChanged),但由于这是一个信号,它只是在 小部件进行常规排序之后触发,所以最后它没有效果 - 顶级项目在函数之前被排序调用。
QTreeWidget 会有一个我可以拦截的sortEvent,但由于情况并非如此,我正在考虑子类化QHeaderView。麻烦的是我不知道它的哪个成员负责排序,我找不到实际的源代码来参考。如果一切都失败了,我会采用模型/视图解决方案。
QTreeWidget 和可能)是模型/视图 suff 的某种复杂实现。无论如何,您可以尝试两种解决方案,我将编辑答案以包含它们。
我根据this 问题的答案找到了解决方案。捕获 sectionClicked 信号并检查单击的列是否是第一个。如果是这样,请强制静态排序方向,然后手动对子项进行排序。还需要一个变量来手动跟踪该列的排序方向。相关代码:
self.lastSortDir = 1
self.treeWidget.header().sectionClicked.connect(self.headerSectionClicked)
def headerSectionClicked(self, colId):
flip = {0:1, 1:0}
if colId == 0:
self.treeWidget.header().setSortIndicator(0,1)
sortDir = flip[self.lastSortDir]
for gId in self.groupWidgets:
self.groupWidgets[gId].sortChildren(0, sortDir)
self.lastSortDir = sortDir
这仍然不理想,主要有两个原因:
每次按第一列排序时,将执行两次排序,一个强制静态方向,另一个用于子项。较大的数据集可能是个问题,而且不够优雅。
第一列的排序指示器将卡住,显示静态排序方向,而不是子级排序的实际方向。主要只是一个外观问题,但它仍然会触发我。
不理想,但考虑到我尝试过的所有其他方法根本不起作用,我现在就接受它。我已经在这上面浪费了足够多的时间,不完美但工作总比完美但不工作要好。
【讨论】: