Sorting Rows by Column#

ttk treeview sorted by column

The Column is Clicked to Sort and Reverse Sort#

See how the header background changes colour when clicked

Say we now wish to add the capability of sorting the rows according to which heading has been clicked, the sort has to maintain all row data, so this is slightly more complicated than a normal sort. A sort_by function is made and placed just after the function select_item. Clicking on the header selects which column's data will be used for sorting:

def sort_by(tree, col, descending):
    # When a column is clicked on sort tree contents .
    # grab values to sort
    data = [(tree.set(child, col), child) for child in tree.get_children('')]

    # reorder data
    data.sort(reverse=descending)
    for indx, item in enumerate(data):
        tree.move(item[1], '', indx)

    # switch the heading so that it will sort in the opposite direction
    tree.heading(col, command=lambda col=col: sort_by(tree, col, int(not descending)))

When inserting the headings add a command option, this has been extended by a lambda function so that the command can give the necessary column information to the sort_by:

#insert headings
for col in tree_columns:
    tree.heading(col, text=col.title(),
        command=lambda c=col: sort_by(tree, c, 0))

When testing this it will sort correctly but the zebra stripes will not be ordered in alternate rows. Add the following lines to the sort_by function.:

# reconfigure tags after ordering
list_of_items = tree.get_children('')
for i in range(len(list_of_items)):
    tree.tag_configure(list_of_items[i], background=backg[i%2])

Click on the column headers and the zebra stripes will remain in one position while the columns change.

Show/Hide Code 04tree_sort.py

"""tkinter ttk treeview
    Shows data in parallel columns, selection, zebra stripes,
    column sorting
    includes workaround for python 3.7 tag colour display
  """

from tkinter import Tk, StringVar
from tkinter.ttk import Frame, Treeview, Style, Label

def fixed_map(option):
    # Fix for setting text colour for Tkinter 8.6.9
    # From: https://core.tcl.tk/tk/info/509cafafae
    #
    # Returns the style map for 'option' with any styles starting with
    # ('!disabled', '!selected', ...) filtered out.

    # style.map() returns an empty list for missing options, so this
    # should be future-safe.
    return [elm for elm in st1.map('Treeview', query_opt=option) if
            elm[:2] != ('!disabled', '!selected')]

root = Tk()
st1 = Style()
st1.theme_use('default')

st1.map('Treeview', foreground=fixed_map('foreground'),
            background=fixed_map('background'))

# function to enable selection
def select_item(evt):
    cur_item = tree.focus()
    lvar.set(tree.item(cur_item)['values'])

def sort_by(tree, col, descending):
    # When a column is clicked on sort tree contents .
    # grab values to sort
    data = [(tree.set(child, col), child) for child in tree.get_children('')]

    # reorder data
    data.sort(reverse=descending)
    for indx, item in enumerate(data):
        tree.move(item[1], '', indx)

    # switch the heading so that it will sort in the opposite direction
    tree.heading(col, command=lambda col=col: sort_by(tree, col, int(not descending)))

    # reconfigure tags after ordering
    list_of_items = tree.get_children('')
    for i in range(len(list_of_items)):
        tree.tag_configure(list_of_items[i], background=backg[i%2])

# headings and data
tree_columns = ['Colours', 'Hash', 'RGB']

tree_data = (('red', '#FF0000', (255,0,0)),
            ('yellow', '#FFFF00', (255,255,0)),
            ('blue', '#0000FF', (0,0,255)),
            ('green', '#00FF00', (0,255,0)),
            ('magenta', '#FF00FF', (255,0,255)),
            ('cyan', '#00FFFF', (0,255,255)))

backg = ["white",'#f0f0ff']

fr0 = Frame(root)
fr0.grid(column=0, row=0, sticky='nsew')

# create Treeview widget
tree = Treeview(fr0, column=tree_columns, show='headings')
tree.grid(column=0, row=0, sticky='nsew')
tree.bind("<<TreeviewSelect>>", select_item)

# insert header, data and tag configuration
for col in tree_columns:
    tree.heading(col, text=col.title(),
       command=lambda c=col: sort_by(tree, c, 0))

for ix, item in enumerate(tree_data):
    item_ID = tree.insert('', 'end', values=item)
    tree.item(item_ID, tags=item_ID)
    tree.tag_configure(item_ID, background=backg[ix%2])

# display selection
lvar = StringVar()
lbl = Label(fr0, textvariable=lvar, text="Ready")
lbl.grid(column=0, row=1, sticky='nsew')

root.mainloop()