0

Based on the example from here: Scrollbar in Tkinter grid

I made a simplified version which is more or less what i need except i would like the columns to always fill the width of the frame as the window is beeing resized.

Without the scrollbar it was super easy, i just added grid_columnconfigure and that worked out of the box, but when I added the scrollbar i couldn't figure out how to get the columns to resize again.

Here is the example:

import tkinter as tk

row = 1


class ProgramWindow(tk.Frame):
    def __init__(self):
        self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
        tk.Frame.__init__(self, self.canvas)
        self.grid(column=0, row=0, sticky='ESW')
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)
        self.grid_columnconfigure(2, weight=1)

        tk.Label(self, text="FirstCol", ).grid(row=0, column=0)
        tk.Label(self, text="SecndCol", ).grid(row=0, column=1)
        tk.Label(self, text="ThirdCol", ).grid(row=0, column=3)

        self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4, 4), window=self)

        self.bind("<Configure>", self.OnFrameConfigure)

    def OnFrameConfigure(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def addrow(self, stuff, otherstuff):
        global row

        var = tk.StringVar(value=stuff)
        entry = tk.Entry(self, textvariable=var)
        entry.grid(row=row, column=0)

        var = tk.StringVar(value=otherstuff)
        entry = tk.Entry(self, textvariable=var)
        entry.grid(row=row, column=1)

        var = tk.StringVar(value="foobar")
        entry = tk.Entry(self, textvariable=var)
        entry.grid(row=row, column=3)

        row += 1


def SomeProg():
    for i in range(20):
        stuff = "Stuff is " + str(i)
        otherstuff = i * 4
        win.addrow(stuff, otherstuff)


root = tk.Tk()
root.title("Stuff")

win = ProgramWindow()
SomeProg()

root.mainloop()
Community
  • 1
  • 1
Michael K.
  • 2,392
  • 4
  • 22
  • 35

1 Answers1

2

I've adapted Bryan Oakley's answer to Adding a scrollbar to a group of widgets in Tkinter so that the frame contained in the canvas fit the width of the canvas.

Canvas window objects have a width option. So, each time the canvas is resized, I pass the new canvas width to this width option using

self.canvas.itemconfigure(<frame tag>, width=self.canvas.winfo_width())

Here is the full code

import tkinter as tk

class Example(tk.Frame):
    def __init__(self, root):

        tk.Frame.__init__(self, root)
        self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
        self.frame = tk.Frame(self.canvas, background="#ffffff")
        self.frame.columnconfigure(0, weight=1)
        self.frame.columnconfigure(1, weight=1)
        self.frame.columnconfigure(2, weight=1)
        self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                  tags="self.frame")

        self.canvas.bind("<Configure>", self.onCanvasConfigure)

        self.populate()

    def populate(self):
        '''Put in some fake data'''
        for i in range(100):
            tk.Entry(self.frame).grid(row=i, column=0, sticky='ew')
            tk.Entry(self.frame).grid(row=i, column=1, sticky='ew')
            tk.Entry(self.frame).grid(row=i, column=2, sticky='ew')

    def onCanvasConfigure(self, event):
        self.canvas.itemconfigure("self.frame", width=self.canvas.winfo_width())
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

if __name__ == "__main__":
    root=tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()
Community
  • 1
  • 1
j_4321
  • 15,431
  • 3
  • 34
  • 61
  • Thanks! Works like a charm! I also realized the Example class can be the Canvas and then you can use instead of self.canvas just self – Michael K. Apr 28 '17 at 20:15
  • Also to understand why i have to do the itemconfigure... is it because i use two different layout manager or because a frame is in a canvas? Because Frame in Frame both with grid layout worked without any manual handling of the width. – Michael K. Apr 28 '17 at 20:18
  • 1
    Here, `self.frame` is not handled by pack, place or grid layouts, but by the canvas. And in this case there is no equivalent of the pack option `fill="x", expand=True` or the grid option `sticky="ew"` combined width `columnconfigure`, so we have to change the width manually. – j_4321 Apr 28 '17 at 20:45