Convert to Function#

tkinter treeview as function

Often we require the treeview to be loaded by an import, rather than explicitly write it out as a script. As the overall script is not too complex it should easily be converted into a function rather than a class.

We can leave the imports as they are, while root and frame can be placed inside if __name__ == "__main__". As the style is referenced by treeview it remains where it is. The csv_file and csv_delimiter variables will be moved to if __name__ == "__main__". The frame will be renamed to page1 to reflect treeview's use in a notebook. If the treeview is being called by another program the calling program will be calling Tk() and driving it using root.mainloop(), so transfer root = Tk() and root.mainloop() to the conditional part. In the fixed_map function there is a Style alias as an argument. Finally call the tree function:

def Tree(fr,csv_file,csv_delimiter=','):
.........
if __name__ == "__main__":
    root = Tk()
    csv_file = '../csv/test.csv'
    csv_delimiter = ';'
    page1 = Frame(root)
    page1.pack(fill='both', expand=False)
    Tree(page1,csv_file,csv_delimiter)
    root.mainloop()

The file is named tree_function, when imported it cannot start with a number.

Show/Hide Code tree_function.py

"""ttk tkinter treeview
    displays table of data in columns, loading from csv file,
    sorts data by column, selection of row

  Parameters
  ----------
  fr : str
    The parent tk widget, normally a Frame.
  csv_file : str
    csv file name to be imported
  out_var : str
    Name of tkvariable that contains output
  csv_delimiter : str
    Type of csv delimiter, defaults to ','

  Returns
  -------
  string
    Information in out_var
  """
from tkinter import Tk, StringVar, font
from tkinter.ttk import Frame, Treeview, Style, Label, Scrollbar, Button
import csv

def fixed_map(st1,option):
    # Fix for setting text colour for Tkinter 8.6.9
    # From: https://core.tcl.tk/tk/info/509cafafae ulfalizer
    #
    # 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')]

def Tree(fr0,csv_file,out_var,csv_delimiter=','):
    st1 = Style()
    st1.theme_use('default')

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

    fact = font.Font(font="TkDefaultFont").metrics('linespace')
    st1.configure('font.Treeview', rowheight=fact,
                  font=font.nametofont("TkDefaultFont"))
    # determine Heading font based on TkDefaultFont
    def_font = font.nametofont('TkDefaultFont')
    font_family = def_font.actual()['family']
    font_size = def_font.actual()['size'] + 1
    st1.configure('font.Treeview.Heading', font=(font_family,font_size,'bold'))

    # function to enable selection
    def select_item(evt):
        curItem = tree.focus()
        lvar.set(tree.item(curItem)['values'])
        out_var.set(tree.item(curItem)['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])

    tree_data = []

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

    with open(csv_file, newline='', encoding='utf-8-sig') as csvfile:
        treeCsv = csv.reader(csvfile, delimiter=csv_delimiter)

        for ix, row in enumerate(treeCsv):
            if ix == 0:
                tree_columns = row
            else:
                tree_data.append(row)

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

    vsb = Scrollbar(fr0,orient="vertical", command=tree.yview)
    vsb.grid(column=1, row=0, sticky='ns')
    hsb = Scrollbar(fr0,orient="horizontal", command=tree.xview)
    hsb.grid(column=0, row=1,  sticky='ew')

    tree.configure(xscrollcommand=hsb.set,yscrollcommand=vsb.set)
    fr0.grid_columnconfigure(0, weight=1)
    fr0.grid_rowconfigure(0, weight=1)

    # insert header, data and tag configuration
    for ix,col in enumerate(tree_columns):
        tree.heading(col, text=col.title(),
            command=lambda c=col: sort_by(tree, c, 0))
        #tree.column(col,stretch=True)
        #tree.column(col,width=font.nametofont('TkHeadingFont').measure(col.title()),
                #stretch=False)
        tree.column(col,width=font.Font(family=font_family,size=font_size, weight="bold").measure(col.title()) + 10,
                stretch=False)
        #print(tree.column(col))

    # insert data row by row, then measure each items' width
    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])

        for indx, val in enumerate(item):
            #ilen = font.Font(family="Segoe UI", size=10, weight="normal").measure(val)
            ilen = font.nametofont('TkDefaultFont').measure(val)
            if tree.column(tree_columns[indx], width=None) < ilen +10:
                tree.column(tree_columns[indx], width=ilen + 10)
            # you should see the widths adjust
            #print('col',tree.column(tree_columns[indx]),ilen)

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

if __name__ == "__main__":
    root = Tk()
    csv_file = '../../csv_data/test.csv'
    csv_delimiter = ';'
    page1 = Frame(root)
    page1.pack(fill='both', expand=False)
    out_var = StringVar()
    Tree(page1,csv_file,out_var,csv_delimiter)
    b2=Button(page1,text='Click after selection', command=lambda:print(out_var.get()))
    b2.grid(column=0, row=3)

    root.mainloop()

Note

Almost every line has been changed due to indentation, only those lines that have a content change or have been transposed have been highlighted.

We will be using tree_function in the chapter on Notebook.