How to Sort Numbers in a Qtreewidget Column

Is it possible to sort numbers in a QTreeWidget column?

You can sort overriding the < operator and changing sort condiction like this.

class TreeWidgetItem : public QTreeWidgetItem {
public:
TreeWidgetItem(QTreeWidget* parent):QTreeWidgetItem(parent){}
private:
bool operator<(const QTreeWidgetItem &other)const {
int column = treeWidget()->sortColumn();
return text(column).toLower() < other.text(column).toLower();
}
};

In this example it ignore the real case, confronting fields in lowercase mode.

How to sort QTreeWidget Column?

Qt does not know that they are file sizes but string so it compares them with those rules. So if you want a custom ordering then you will have to override the __lt__ method of the QTreeWidgetItem. For the comparison you can make a mapping to convert the units to numbers that can be compared but to avoid that work I prefer to use a library that does it: datasize (python -m pip install datasize)

from datasize import DataSize

SIZE_COLUMN = 2

class TreeWidgetItem(QtGui.QTreeWidgetItem):
def __lt__(self, other):
column = self.treeWidget().sortColumn()
if column == SIZE_COLUMN:
# DataSize does not accept space between the quantity and the unit,
# so we eliminate the spaces:
text_with_spaces = self.text(column).replace(" ", "")
other_text_with_spaces = other.text(column).replace(" ", "")
return DataSize(text_with_spaces) < DataSize(other_text_with_spaces)
return super(TreeWidgetItem, self).__lt__(other)
for filepath, size, owner in folderdata:
child_item = TreeWidgetItem(top_item, ["", filepath, size, owner])

If you don't want to use datasize then you will have to implement your own converter, something like(See Python - Parse human-readable filesizes into bytes for other examples):

SIZE_COLUMN = 2
SIZE_MAPPING = {"MiB": 2 ** 20, "KiB": 2 ** 10}

def converter(text):
string_value, unit = text.split()
return float(string_value) * SIZE_MAPPING[unit]

class TreeWidgetItem(QtGui.QTreeWidgetItem):
def __lt__(self, other):
column = self.treeWidget().sortColumn()
if column == SIZE_COLUMN:
return converter(self.text(column)) < converter(other.text(column))
return super().__lt__(other)

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

Foreword

I'm not a big fan of long answers, and even loss a fan of long pieces of hard to read code, but these are none the less the solution(s) that I came up with when looking for an answer to this question myself a while back.

Simple

This first piece of code is basically a very simplified solution of what I used in the end. It's more efficient and, more importantly, much more easy to read and understand.

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

Extended

I also had the need to...

  • Sort some columns as integers and/or decimal.Decimal type objects.
  • Mix ascending and descending order (i.e. mind the Qt.SortOrder set for each column)

The following example is therefore what I ended up using myself.

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

Usage

The static method SORT_COL_KEY and the SORT_COL_KEYS class attribute of the above stated MultisortTreeWidgetItem class also allow for other values than those returned by self.text(N) to be used, for example a list returned by self.data().

The following example sort the text in the rows of the first column as integers and sorts the rows of the third column by the corresponding object in a list returned by self.data(). All other columns is sorted by the item.text() values, sorted as strings.

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
}

Create a MultisortTreeWidget object and add it to a layout, then create UsageExampleItems and add them to the MultisortTreeWidget.

This solution "remembers" the columns and sort order used previously. So, if you want to sort the items in a UsageExampleItems widget by the values in the first column, and have rows that share a value to be sorted by the second column among themselves, then you would first click on the header item of the second column and then proceed to click on the header item of the first column.

Sort a PySide.QtGui.QTreeWidget by an alpha numeric column

You can re-implement the less-than operator of your table-widget items and use whatever algorithm you like to control sorting:

class TableWidgetItem(QtGui.QTableWidgetItem):
def __lt__(self, other):
return funky_sort_key(self.text(), other.text())

Using this method, you don't even need to sort by text - you could also sort by the items' data (or whatever).

Automatically sorting on insert in QTreeWidget

If your tree widget is called treeWidget, you should be able to call the header() method, which is from QTreeWidget's parent QTreeView, then sortIndicatorOrder() from the QHeaderView class:

treeWidget->header()->sortIndicatorOrder()

With this, you know the user's current sort order, and you can apply your sort on insert according to this.

QTreeWidget - excluding top level items from sort

The simplest solution is to create a subclass of QTreeWidgetItem and reimplement its __lt__ method so that it always returns False. Qt uses a stable sort algorithm, so this means the items will always retain their original order. This subclass should be used for the top-level items only.

Here is a working demo that seems to meet your spec:

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

sort qtreewidget toplevel item base on a child data

By default, tree widget sorts items according to their texts, however you can change it by overriding the operator<() of the QTreeWidgetItem. Below is the example of custom QTreeWidgetItem with specific operator (see comments):

class TreeWidgetItem : public QTreeWidgetItem
{
public:
// The constructors. Add more, if needed.
TreeWidgetItem(QTreeWidget *parent, const QStringList &strings,
int type = Type)
: QTreeWidgetItem(parent, strings, type)
{}

TreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings,
int type = Type)
: QTreeWidgetItem(parent, strings, type)
{}

// Compares two tree widget items. The logic can be changed.
bool operator<(const QTreeWidgetItem& other) const
{
// Get the price - the first child node
int price1 = 0;
if (childCount() > 0)
{
QTreeWidgetItem *firstChild = child(0);
price1 = firstChild->text(0).toInt();
}

// Get the second price - the first child node
int price2 = 0;
if (other.childCount() > 0)
{
QTreeWidgetItem *firstChild = other.child(0);
price2 = firstChild->text(0).toInt();
}
// Compare two prices.
return price1 < price2;
}
};

And here is how this class can be used with QTreeWidget:

// The sortable tree widget.
QTreeWidget tw;
tw.setSortingEnabled(true);
QTreeWidgetItem *item1 = new TreeWidgetItem(&tw, QStringList() << "Item1");
QTreeWidgetItem *child1 = new TreeWidgetItem(item1, QStringList() << "10");

QTreeWidgetItem *item2 = new TreeWidgetItem(&tw, QStringList() << "Item2");
QTreeWidgetItem *child2 = new TreeWidgetItem(item2, QStringList() << "11");
tw.show();

stable sorting QTreeWidgetItems in QTreeWidget?

Are you using QItemSelectionModel to do this, or did you write it yourself?

  • If you wrote it yourself I would suggest using QItemSelectionModel.
  • If you didn't, it sounds like you want a custom sorting algorithm which would require creating a derived QTreeWidget, if you are doing that, you might as well just use QTreeView and a custom QAbstractItemModel.

Also, if you have two views of the same data, I would HIGHLY recommended using the Model/View framework and a QTreeView.



Related Topics



Leave a reply



Submit