Question

I'm using the ttk Treeview widget to implement a folder/path selection dialog. It's all working as expected except that my horizontal scrollbar won't activate. No matter how wide the folder path goes horizontally, and no matter how narrow the window, the horizontal slider never appears. Vertical scrolling is working perfectly though.

I'm figuring it's either some kind of limitation when you only use one column in the treeview, or just a newbie mistake with configuring and connecting the widgets. I'd bet on the latter.

Example with dialog widened to show full folder depth:

full width

Dialog narrowed to the point where horizontal scrolling should activate (but doesn't):

narrowed width

Here's my GUI layout code:

winDirSel = tk.Toplevel()
winDirSel.title('Select Test Directory...')
tvwDirSel = ttk.Treeview(winDirSel,
                         height=10,padding=3,
                         show='tree')
lblTestDir = tk.Label(winDirSel, relief=tk.SUNKEN,
                      justify=tk.LEFT, anchor=tk.W,
                      textvariable=ctrlTestDir,width=80)
scbHDirSel = ttk.Scrollbar(winDirSel,
                           orient=tk.HORIZONTAL,
                           command=tvwDirSel.xview)
scbVDirSel = ttk.Scrollbar(winDirSel,
                           orient=tk.VERTICAL,
                           command=tvwDirSel.yview)
tvwDirSel.configure(xscrollcommand=scbHDirSel.set,
                    yscrollcommand=scbVDirSel.set)
lblTestDir.grid(row=0,column=0,sticky=tk.EW)
tvwDirSel.grid(row=1,column=0,sticky=tk.NSEW)
scbVDirSel.grid(row=1,column=1,sticky=tk.NS)
scbHDirSel.grid(row=2,column=0,sticky=tk.EW)
winDirSel.rowconfigure(1,weight=1)
winDirSel.columnconfigure(0,weight=1)
Was it helpful?

Solution

OK, after some playing with minwidth and stretch, I think I have a better handle on it. The horizontal scrolling is triggered by the column-edge going out of the window's bounds, not the content of the column. So you can use these parameters to force the column to be wider and thus force the scrolling.

The problem though is that you then lose the automatic adjustment of the column width to suit the width of the tree itself. You either have to force it very wide to accommodate any (assumed) likely folder depth, or you live with folder names getting truncated at the right boundary of the column.

So bottom line: it's just a limitation of the widget itself. (At least with respect to its behavior on my platform, MS Windows.)

OTHER TIPS

Here's what I finally came up with to display a TreeView of files which are lazy-loaded (thanks to this answer) which is inside a PanedWindow (SplitterWindow in wxPython terms) along with a Notebook. The scrollbars are auto-displayed/hidden as needed, thanks to this example.

import os
import Tkinter as tk
import ttk as ttk
from ScrolledText import ScrolledText

class App(object):
    def __init__(self, master, path):
        splitter = tk.PanedWindow(master, orient=tk.HORIZONTAL)
        # left-side
        frame_left = tk.Frame(splitter)
        self.tree = ttk.Treeview(frame_left, show='tree')
        ysb = ttk.Scrollbar(frame_left, orient='vertical', command=self.tree.yview)
        xsb = ttk.Scrollbar(frame_left, orient='horizontal', command=self.tree.xview)
        # right-side
        frame_right = tk.Frame(splitter)
        nb = ttk.Notebook(frame_right)
        page1 = ttk.Frame(nb)
        page2 = ttk.Frame(nb)
        text = ScrolledText(page2)

        # overall layout
        splitter.add(frame_left)
        splitter.add(frame_right)
        splitter.pack(fill=tk.BOTH, expand=1)
        # left-side widget layout
        self.tree.grid(row=0, column=0, sticky='NSEW')
        ysb.grid(row=0, column=1, sticky='ns')
        xsb.grid(row=1, column=0, sticky='ew')
        # left-side frame's grid config
        frame_left.columnconfigure(0, weight=1)
        frame_left.rowconfigure(0, weight=1)
        # right-side widget layout
        text.pack(expand=1, fill="both")
        nb.add(page1, text='One')
        nb.add(page2, text='Two')
        nb.pack(expand=1, fill="both")

        # setup
        self.tree.configure(yscrollcommand=lambda f, l:self.autoscroll(ysb,f,l), xscrollcommand=lambda f, l:self.autoscroll(xsb,f,l))
        # use this line instead of the previous, if you want the scroll bars to always be present, but grey-out when uneeded instead of disappearing
        #self.tree.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set)
        self.tree.heading('#0', text='Project tree', anchor='w')
        self.tree.column("#0",minwidth=1080, stretch=True)
        # add default tree node
        abspath = os.path.abspath(path)
        self.nodes = dict()
        self.insert_node('', abspath, abspath)
        self.tree.bind('<<TreeviewOpen>>', self.open_node)

    def autoscroll(self, sbar, first, last):
        """Hide and show scrollbar as needed."""
        first, last = float(first), float(last)
        if first <= 0 and last >= 1:
            sbar.grid_remove()
        else:
            sbar.grid()
        sbar.set(first, last)

    def insert_node(self, parent, text, abspath):
        node = self.tree.insert(parent, 'end', text=text, open=False)
        if os.path.isdir(abspath):
            self.nodes[node] = abspath
            self.tree.insert(node, 'end')

    def open_node(self, event):
        node = self.tree.focus()
        abspath = self.nodes.pop(node, None)
        if abspath:
            self.tree.delete(self.tree.get_children(node))
            for p in os.listdir(abspath):
                self.insert_node(node, p, os.path.join(abspath, p))


if __name__ == '__main__':
    root = tk.Tk()
    root.geometry("800x600")
    app = App(root, path='.')
    root.mainloop()
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tk_font

class TreeListBox:

    def __init__(self, master, root, dict_group):
        self.master = master
        self.root = root
        self.dict_group = dict_group
        self.level = 0
        self.setup_widget_tree()
        self.build_tree(self.root, '')

    def setup_widget_tree(self):
        container_tree = tk.Frame(self.master, width=250, height=300)
        container_tree.propagate(False)
        container_tree.pack(side="left", fill='y')
        self.tree = ttk.Treeview(container_tree, show="tree", selectmode='browse')
        fr_y = tk.Frame(container_tree)
        fr_y.pack(side='right', fill='y')
        tk.Label(fr_y, borderwidth=1, relief='raised', font="Arial 8").pack(side='bottom', fill='x')
        sb_y = tk.Scrollbar(fr_y, orient="vertical", command=self.tree.yview)
        sb_y.pack(expand='yes', fill='y')
        fr_x = tk.Frame(container_tree)
        fr_x.pack(side='bottom', fill='x')
        sb_x = tk.Scrollbar(fr_x, orient="horizontal", command=self.tree.xview)
        sb_x.pack(expand='yes', fill='x')
        self.tree.configure(yscrollcommand=sb_y.set, xscrollcommand=sb_x.set)
        self.tree.pack(fill='both', expand='yes')

    def build_tree(self, parent, id_stroki):
        self.level += 1
        id = self.tree.insert(id_stroki, 'end', text=parent)
        # -----------------
        col_w = tk_font.Font().measure(parent)
        if col_w > 1000:
            col_w -= 400
        elif col_w > 500:
            col_w -= 200
        elif col_w > 300:
            col_w -= 100
        col_w = col_w + 25 * self.level
        if col_w > self.tree.column('#0', 'width'):
            self.tree.column('#0', width=col_w)
        # -----------------
        for element in sorted(self.dict_group[parent]):
            self.build_tree(element, id)
        self.level -= 1

if __name__ == '__main__':
    dict_group = {'Nomenclature': ['ABC1', 'ABC2'],
                  'ABC1': ['ABC3', 'ABC4'],
                  'ABC2': ['ABC5'],
                  'ABC3': ['ABC______________________________________6'],
                  'ABC4': ['ABC--------------------------------------8'],
                  'ABC5': ['ABC######################################9'],
                  'ABC______________________________________6': [],
                  'ABC--------------------------------------8': [],
                  'ABC######################################9': []
                  }
    root = tk.Tk()
    myTest = TreeListBox(root, 'Nomenclature', dict_group)
    root.mainloop()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top