Is there a way to sort the entries in a Tk Treeview by clicking the column? Surprisingly, I could not find any documentation/tutorial for this.
5 Answers
patthoyts from #tcl pointed out that the TreeView Tk demo program had the sort functionality. Here's the Python equivalent of it:
def treeview_sort_column(tv, col, reverse):
l = [(tv.set(k, col), k) for k in tv.get_children('')]
l.sort(reverse=reverse)
# rearrange items in sorted positions
for index, (val, k) in enumerate(l):
tv.move(k, '', index)
# reverse sort next time
tv.heading(col, command=lambda: \
treeview_sort_column(tv, col, not reverse))
[...]
columns = ('name', 'age')
treeview = ttk.TreeView(root, columns=columns, show='headings')
for col in columns:
treeview.heading(col, text=col, command=lambda: \
treeview_sort_column(treeview, col, False))
[...]
madonius is right, but here you have the full example and a proper, understandable explanation
The answer provided by Sridhar Ratnakumar does not work in python3 (and apparently in python2.7): since the variable is passed by reference, all lambdas end up referring to the same, last, element in columns.
You just need to change this for loop:
for col in columns:
treeview.heading(col, text=col, command=lambda _col=col: \
treeview_sort_column(treeview, _col, False))
And the same change has to be applied to the lambda function inside treeview_sort_column
So the complete solution would look like this:
def treeview_sort_column(tv, col, reverse):
l = [(tv.set(k, col), k) for k in tv.get_children('')]
l.sort(reverse=reverse)
# rearrange items in sorted positions
for index, (val, k) in enumerate(l):
tv.move(k, '', index)
# reverse sort next time
tv.heading(col, text=col, command=lambda _col=col: \
treeview_sort_column(tv, _col, not reverse))
[...]
columns = ('name', 'age')
treeview = ttk.TreeView(root, columns=columns, show='headings')
for col in columns:
treeview.heading(col, text=col, command=lambda _col=col: \
treeview_sort_column(treeview, _col, False))
[...]
I just encountered the same issue while trying to create a view for DB,
Inspired by Sridhar Ratnakumar Answer,
I rather take the same principles that he did and to upgrade the class Treeview.
class MyTreeview(ttk.Treeview):
def heading(self, column, sort_by=None, **kwargs):
if sort_by and not hasattr(kwargs, 'command'):
func = getattr(self, f"_sort_by_{sort_by}", None)
if func:
kwargs['command'] = partial(func, column, False)
return super().heading(column, **kwargs)
def _sort(self, column, reverse, data_type, callback):
l = [(self.set(k, column), k) for k in self.get_children('')]
l.sort(key=lambda t: data_type(t[0]), reverse=reverse)
for index, (_, k) in enumerate(l):
self.move(k, '', index)
self.heading(column, command=partial(callback, column, not reverse))
def _sort_by_num(self, column, reverse):
self._sort(column, reverse, int, self._sort_by_num)
def _sort_by_name(self, column, reverse):
self._sort(column, reverse, str, self._sort_by_name)
def _sort_by_date(self, column, reverse):
def _str_to_datetime(string):
return datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
self._sort(column, reverse, _str_to_datetime, self._sort_by_date)
...
# Some code
...
treeview.heading('number', text='number', sort_by='num')
treeview.heading('name', text='name', sort_by='name')
treeview.heading('date', text='date', sort_by='date')
Just puting this here :)
make this small change to the function if you have integers in your table it will look like this.
def treeview_sort_column(treeview: ttk.Treeview, col, reverse: bool):
"""
to sort the table by column when clicking in column
"""
try:
data_list = [
(int(treeview.set(k, col)), k) for k in treeview.get_children("")
]
except Exception:
data_list = [(treeview.set(k, col), k) for k in treeview.get_children("")]
data_list.sort(reverse=reverse)
# rearrange items in sorted positions
for index, (val, k) in enumerate(data_list):
treeview.move(k, "", index)
# reverse sort next time
treeview.heading(
column=col,
text=col,
command=lambda _col=col: treeview_sort_column(
treeview, _col, not reverse
),
)