如何按多列对 QTreeWidget 或 QTableWidget 进行排序(以及如何将这些列排序为数值)?

How do I sort sort a QTreeWidget or QTableWidget by multiple columns (and how do I sort these columns as numerical values)?

假设我有一个包含三列的 QTreeWidget。其中两个具有字符串值,第三个具有整数值,所有这些都可能在每一列中出现不止一次。

然后我希望能够按 usernameproduct 对这些项目进行排序,并让共享这些值的行按数量.

排序

附带说明一下,我还需要能够将假设 quantity 的值排序为数值。

假设我在前面的示例中按数量排序了三行,并且这些行具有这些值:

然后我希望这些行以相同的顺序排序,而不是如果它们被排序为字符串值:

如何使用 PyQt5 实现这种组合?

前言

我不喜欢冗长的答案,甚至不喜欢冗长的难以阅读的代码,但这些 none 我想出的解决方案越少前段时间自己在寻找这个问题的答案时。

简单

第一段代码基本上是我最终使用的非常简化的解决方案。它更高效,更重要的是,更易于阅读和理解。

from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem


class SimpleMultisortTreeWidget(QTreeWidget):

    def __init__(self, *a, **k):
        super().__init__(*a, **k)
        self._csort_order = []
        self.header().sortIndicatorChanged.connect(self._sortIndicatorChanged)
    
    def _sortIndicatorChanged(self, n, order):
        try:
            self._csort_order.remove(n)
        except ValueError:
            pass
        self._csort_order.insert(0, n)
        self.sortByColumn(n, order)


class SimpleMultisortTreeWidgetItem(QTreeWidgetItem):
    
    def __lt__(self, other):
        corder = self.treeWidget()._csort_order
        return list(map(self .text, corder)) < \
               list(map(other.text, corder))

扩展

我也有需要...

  • 将某些列排序为整数 and/or decimal.Decimal 输入 objects.
  • 混合升序和降序(即注意每列的 Qt.SortOrder 集)

因此,以下示例是我最终使用的示例。

from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem


class MultisortTreeWidget(QTreeWidget):
    u"""QTreeWidget inheriting object, to be populated by
    ``MultisortTreeWidgetItems``, that allows sorting of multiple columns with
    different ``Qt.SortOrder`` values.
    """

    def __init__(self, *arg, **kw):
        r"Pass on all positional and key word arguments to super().__init__"
        super().__init__(*arg, **kw)
        self._csort_corder = []
        self._csort_sorder = []
        self.header().sortIndicatorChanged.connect(
            self._sortIndicatorChanged
        )

    def _sortIndicatorChanged(self, col_n, order):
        r"""
        Update private attributes to reflect the current sort indicator.
        
        (Connected to self.header().sortIndicatorChanged)
        
        :param col_n: Sort indicator indicates column with this index to be
        the currently sorted column.
        :type  col_n: int
        :param order: New sort order indication. Qt enum, 1 or 0.
        :type  order: Qt.SortOrder
        """
        # The new and current column number may, or may not, already be in the
        # list of columns that is used as a reference for their individual
        # priority.
        try:
            i = self._csort_corder.index(col_n)
        except ValueError:
            pass
        else:
            del self._csort_corder[i]
            del self._csort_sorder[i]
        # Force current column to have highest priority when sorting.
        self._csort_corder.insert(0, col_n)
        self._csort_sorder.insert(0, order)
        self._csort = list(zip(self._csort_corder,self._csort_sorder))
        # Resort items using the modified attributes.
        self.sortByColumn(col_n, order)


class MultisortTreeWidgetItem(QTreeWidgetItem):
    r"""QTreeWidgetÍtem inheriting objects that, when added to a
    MultisortTreeWidget, keeps the order of multiple columns at once. Also
    allows for column specific type sensitive sorting when class attributes
    SORT_COL_KEYS is set.
    """
    
    @staticmethod
    def SORT_COL_KEY(ins, c):
        return ins.text(c)

    SORT_COL_KEYS = []
    
    def __lt__(self, other):
        r"""Compare order between this and another MultisortTreeWidgetItem like
        instance.
 
        :param other: Object to compare against.
        :type  other: MultisortTreeWidgetItem.
        :returns: bool
        """
        # Fall back on the default functionality if the parenting QTreeWidget
        # is not a subclass of MultiSortTreeWidget or the SortIndicator has not
        # been changed.
        try:
            csort = self.treeWidget()._csort
        except AttributeError:
            return super(MultisortTreeWidgetItem, self).__lt__(other)
        # Instead of comparing values directly, place them in two lists and
        # extend those lists with values from columns with known sort order.
        order = csort[0][1]
        left  = []
        right = []
        for c, o in csort:
            try:
                key = self.SORT_COL_KEYS[c]
            except (KeyError, IndexError):
                key = self.SORT_COL_KEY
            #  Reverse sort order for columns not sorted according to the
            # current sort order indicator.
            if o == order:
                left .append(key(self , c))
                right.append(key(other, c))
            else:
                left .append(key(other, c))
                right.append(key(self , c))
        return left < right

用法

上述MultisortTreeWidgetItemclass的静态方法SORT_COL_KEYSORT_COL_KEYSclass属性也允许[=返回的值以外的其他值self.text(N) 被使用,例如 self.data().

返回的列表

以下示例将第一列的行中的文本按整数排序,并按 self.data() 返回的列表中相应的 object 对第三列的行进行排序。所有其他列按 item.text() 值排序,按字符串排序。

class UsageExampleItem(MultisortTreeWidgetItem):
    
    SORT_COL_KEYS = {
        0: lambda item, col: int(item.text(col)),
        2: lambda item, col: item.data()[col],  
        5: lambda item, col: int(item.text(col) or 0) # Empty str defaults to 0
    }

创建 MultisortTreeWidget object 并将其添加到布局,然后创建 UsageExampleItems 并将它们添加到 MultisortTreeWidget

此解决方案“记住”以前使用的列和排序顺序。因此,如果您想要根据第一列中的值对 UsageExampleItems 小部件中的项目进行排序,并且让共享一个值的行彼此之间按照第二列进行排序,那么您首先要单击 header 第二列的项目,然后继续单击第一列的 header 项目。