Adding Scrollbars#

ttk treeview with scrollbars

Treeview with Scrollbars Activated#

Adjust the window size to see the scrollbars activate.

Sometimes there is too much data to display in the current window, then it is necessary to use Scrollbars. Import Scrollbar in ttk, insert them just after the Treeview clause. Configure Treeview to take the scrollbars then give equal weight to both rows and columns, this ensures that any changes are equally divided between the orientations. Both the scrollbars require a sticky directive only for their orientation - we want them to become longer, not fatter.

Make sure that the scrollbars are positioned one row and one column after the Treeview grid, and that the label is one row lower than the horizontal scrollbar:

from tkinter.ttk import Frame, Treeview, Style, Label, Scrollbar
.........
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)
fr.grid_columnconfigure(0, weight=1)
fr.grid_rowconfigure(0, weight=1)
......
lbl = Label(fr0, textvariable=lvar, text="Ready")
lbl.grid(column=0, row=2, sticky='nsew')

Duplicate the changes for the vertical scrollbar. The horizontal scrollbar will probably stay greyed out and not accessible. Most examples on the internet will show vertical scrollbars as they operate without too much trouble. Horizontal scrollbars seem to be a problem. Keep the grid system for the treeview and scrollbars, but change the frame to a pack managed layout, also ensure that the header is configured with stretch=False and leave out the stretch option in the data configuration. Now when the window is compressed the scrollbars activate. (I know this seems to be just hocus pocus and not completely obvious but it seems to work):

fr.pack(fill='both', expand=False)

Show/Hide Code 06tree_scrollbars.py

"""tkinter ttk treeview
    Shows data in parallel columns, selection, zebra stripes,
    column sorting, adjusting heading, column widths and row heights,
    scrollbars
    includes workaround for python 3.7 tag colour display
  """

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

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')
#root.grid_columnconfigure(0, weight=1)
#root.grid_rowconfigure(0, weight=1)

st1.map('Treeview', foreground=fixed_map('foreground'),
            background=fixed_map('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):
    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', 'Extra long header']

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)),
            ('foo', 'bar', 'bong', 'ding a ling ping'))

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

fr0 = Frame(root)
fr0.pack(fill='both', expand=False)  #grid(column=0, row=0, sticky='nsew')  #

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

root.mainloop()

To enable the horizontal scrollbars using the grid method throughout, root is instructed to expand by adding:

root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)

Remember to change the LabelFrame to the grid method.