【问题标题】:How do I sort sort a QTreeWidget or QTableWidget by multiple columns (and how do I sort these columns as numerical values)?如何按多列对 QTreeWidget 或 QTableWidget 进行排序(以及如何将这些列排序为数值)?
【发布时间】:2023-03-27 15:03:01
【问题描述】:

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

  • 用户名(字符串)
  • 产品(字符串)
  • 数量(整数)

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

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

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

  • 1
  • 2
  • 10

然后我希望这些行以相同的顺序排序,不是,就像它们被排序为字符串值一样:

  • 1
  • 10
  • 2

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

【问题讨论】:

标签: python pyqt qtablewidget qtreewidget


【解决方案1】:

前言

我不喜欢冗长的答案,甚至不喜欢长篇难以阅读的代码,但这些仍然是我在寻找答案时想出的解决方案这个问题我自己前一阵子。

简单

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

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))

扩展

我也有需要...

  • 将某些列排序为整数和/或decimal.Decimal 类型的对象。
  • 混合升序和降序(即注意为每一列设置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

用法

上述MultisortTreeWidgetItem 类的静态方法SORT_COL_KEYSORT_COL_KEYS 类属性还允许使用除self.text(N) 返回的值之外的其他值,例如self.data() 返回的列表。

以下示例将第一列的行中的文本排序为整数,并根据self.data() 返回的列表中的相应对象对第三列的行进行排序。所有其他列按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 对象并将其添加到布局中,然后创建UsageExampleItems 并将它们添加到MultisortTreeWidget

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

【讨论】:

  • sortIndicatorChanged 事件调用 sortByColumn 在我的情况下会导致无休止的递归。
猜你喜欢
  • 2021-06-20
  • 1970-01-01
  • 2018-10-18
  • 2010-09-27
  • 2010-11-18
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 2016-03-08
相关资源
最近更新 更多