Search code examples
pythontkinterttk

Disable tree view from filling the window after update


I have very simple grid layout with two columns, where first column should display some text, and the second to show tree view:

#! python3

from random import randint
import tkinter as tk
from tkinter import ttk
from tkinter.constants import *


class Application(ttk.Frame):

    def __init__(self, root):
        self.root = root
        self.root.resizable(0, 0)
        self.root.grid_columnconfigure(0, weight=1)
        self.root.grid_columnconfigure(1, weight=3)
        self.init_widgets()
        self.arrange_grid()

    def init_widgets(self):
        self.text_frame = ttk.Labelframe(self.root, text='Info')
        self.button = ttk.Button(self.root, text='Process', command=self.on_button)
        self.tree = ttk.Treeview(self.root)
        self.scroll = ttk.Scrollbar(self.root, orient=HORIZONTAL, command=self.tree.xview)
        self.tree.configure(xscrollcommand=self.scroll.set)

    def arrange_grid(self):
        self.text_frame.grid(row=0, sticky=NSEW)
        self.button.grid(row=0, sticky=N, pady=32)
        self.tree.grid(row=0, column=1, sticky=NSEW)
        self.scroll.grid(row=0, column=1, sticky=(S, W, E))

    def on_button(self):
        headers = list(range(20))
        rows = [[randint(0, 100)] * len(headers) for i in headers]
        self.tree["columns"] = headers
        for i, row in enumerate(rows):
            self.tree.insert("", i, values=row)


if __name__ == '__main__':
    root = tk.Tk()
    app = Application(root)
    root.mainloop()

When I click on a "Process" button, tree view is populated with data, but at the same time it resizes the root window and fills whole space.

How can I instruct ttk tree view, to remain it's size after populating with data?


Solution

  • The treeview will grow to fit all of its columns, unless constrained by the window. The window will grow to fit all of it children unless you give it a fixed size. What is happening is that you're giving the treeview many columns, causing it to grow. Because it grows, the window grows because you haven't constraint its growth.

    There are several solutions. Perhaps the simplest solution is to put the tree in a frame so that you can give it an explicit width and height. The key to this is to make the frame control the size of its children rather than the other way around. This is done by turning geometry propagation off.

    First, start by creating a frame, and then putting the tree in the frame. We can also put the scrollbar in the frame so that we can treat the tree and scrollbar as a single unit.

    self.tree_frame = tk.Frame(self.root, width=400, height=200)
    self.tree = ttk.Treeview(self.treeframe)
    self.scroll = ttk.Scrollbar(self.tree_frame, orient=HORIZONTAL, command=self.tree.xview)
    self.tree.configure(xscrollcommand=self.scroll.set)
    

    Next, add the treeview and scrollbar to the frame. You can use any of pack, place or grid; I find pack superior for a top-to-bottom layout. We also use pack_propagate to turn off geometry propagation (meaning: the frame width and height are honored):

    self.tree_frame.pack_propagate(0)
    self.scroll.pack(side="bottom", fill="x")
    self.tree.pack(side="top", fill="both", expand=True)
    

    With that, you need to modify your arrange_grid to put the frame in the root window, and then ignore the scrollbar since it's already packed in the frame:

    def arrange_grid(self):
        self.text_frame.grid(row=0, sticky=NSEW)
        self.button.grid(row=0, sticky=N, pady=32)
        self.tree_frame.grid(row=0, column=1, sticky=NSEW)
    

    Note: you've turned off the ability for the user to resize the window. I recommend avoiding this -- the user usually knows better what size they want the window. Instead, you should configure your GUI to properly resize when the user resizes the window.

    Since you're using grid, all you have to do is tell tkinter which rows and columns get any extra space caused by the user resizing the window. Since everything is in a single row, you merely need to give that row a weight:

    root.grid_rowconfigure(0, weight=1)