Python Treeview alternate row colouring after sorting

Viewed 91

Is there a way that the alternative row colouring can be maintained after sorting? My treeview has 2,000+ rows and would like to know if there is any solution other than retagging all the rows each time a column is sorted. When you click on any column, the row colouring gets mixed up.

Environment: Python 3.10.0 Windows 21H1

import tkinter as objTK
from tkinter import ttk as objTTK
from functools import partial

# Custom column sort for treeview
class MyTreeview(objTTK.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)
            # End of if
        # End of if
        return super().heading(column, **kwargs)
    # End of heading()

    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)
        # End of for loop
        self.heading(column, command=partial(callback, column, not reverse))
    # End of _sort()

    def _sort_by_name(self, column, reverse):
        self._sort(column, reverse, str, self._sort_by_name)
    # End of _sort_by_num()
# End of class MyTreeview

objWindow = objTK.Tk()

arrlbHeader = ["Type" , "Description"]
treeview = MyTreeview(columns=arrlbHeader, show="headings")
arrRows = [
    ["Expenses", "Gen"],
    ["Expenses", "Aug"],
    ["Expenses", "Aug"],
    ["Income", "Aug"],
    ["Expenses", "Aug"]
]
arrColWidth = [70, 80]
arrColAlignment = ["center", "e"]

# Column header and attributes
arrSortType = ["name", "name"]
for iCount in range(len(arrlbHeader)):
    strHdr = arrlbHeader[iCount]
    treeview.heading(strHdr, text=strHdr.title(), sort_by=arrSortType[iCount])
    treeview.column(arrlbHeader[iCount], width=arrColWidth[iCount], stretch=True, anchor=arrColAlignment[iCount])
# End of for loop

treeview.pack()

# Row colouring tags
treeview.tag_configure("tgOddRow", background="white")
treeview.tag_configure("tgEvenRow", background="blue")      

# Insert rows
for iCount in range(len(arrRows)):
    if iCount % 2 == 0:
        treeview.insert("", "end", values=arrRows[iCount], tags="tgEvenRow")
    else:
        treeview.insert("", "end", values=arrRows[iCount], tags="tgOddRow")
# End of for loop

objWindow.bind("<Escape>", lambda funcWinSer: objWindow.destroy())

objWindow.mainloop()
1 Answers

The solution is to retag all of the items. The treeview widget can retag a couple thousand rows in a tiny fraction of a second.

Here's a simple example. It assumes you don't have items nested under other items. If you do, it's fairly straightforward to account for that.

def retag():
    tag = "tgOddRow"
    for iid in treeview.get_children(""):
        tag = "tgOddRow" if tag == "tgEvenRow" else "tgEvenRow"
        treeview.item(iid, tags=(tag,))

When I run that function on a treeview with 10,000 rows, it takes well under 100ms.

Related