1
votes

I am using the following code for having a scrollable frame. I want now each column in the grid to fill the whole window horizontally, also when the window size changes.

For this approach I have always used frame.grid_columnconfigure(col_num, weight=1) and added the sticky="ew" parameter to the .grid(row, col) command when placing an element.

For some reason (maybe has to do with the canvas window) this approach does not work in the following code.

import tkinter as tk

# ************************
# Scrollable Frame Class
# ************************


class ScrollFrame(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)  # create a frame (self)

        # place canvas on self
        self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
        # place a frame on the canvas, this frame will hold the child widgets
        self.viewPort = tk.Frame(self.canvas, background="#ffffff")
        # place a scrollbar on self
        self.vsb = tk.Scrollbar(self, orient="vertical",
                                command=self.canvas.yview)
        # attach scrollbar action to scroll of canvas
        self.canvas.configure(yscrollcommand=self.vsb.set)

        # pack scrollbar to right of self
        self.vsb.pack(side="right", fill="y")
        # pack canvas to left of self and expand to fil
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4, 4), window=self.viewPort, anchor="nw",  # add view port frame to canvas
                                  tags="self.viewPort")

        # bind an event whenever the size of the viewPort frame changes.
        self.viewPort.bind("<Configure>", self.onFrameConfigure)

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox(
            "all"))  # whenever the size of the frame changes, alter the scroll region respectively.


# ********************************
# Example usage of the above class
# ********************************

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

        tk.Frame.__init__(self, root)
        self.scrollFrame = ScrollFrame(self)  # add a new scrollable frame.

        # try to make the columns filling the entire frame
        self.scrollFrame.viewPort.grid_columnconfigure(0, weight=1)
        self.scrollFrame.viewPort.grid_columnconfigure(1, weight=1)

        # Now add some controls to the scrollframe.
        # NOTE: the child controls are added to the view port (scrollFrame.viewPort, NOT scrollframe itself)
        for row in range(100):
            a = row
            tk.Label(self.scrollFrame.viewPort, text="%s" % row, width=3, borderwidth="1",
                     relief="solid").grid(row=row, column=0, sticky="ew")
            t = "this is the second column for row %s" % row
            tk.Button(self.scrollFrame.viewPort, text=t, command=lambda x=a: self.printMsg(
                "Hello " + str(x))).grid(row=row, column=1, sticky="ew")

        # when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort)
        self.scrollFrame.pack(side="top", fill="both", expand=True)

    def printMsg(self, msg):
        print(msg)


if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Many thanks beforehand for any suggestions!

1

1 Answers

1
votes

The problem isn't with your columns expanding and contracting, it's the fact that the frame they are in isn't growing or shrinking.

To solve this you need to bind to the <Configure> event of the canvas. When it changes size you can force the inner frame to be the same width as the canvas.

First, establish the binding:

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

Next, define the bound function to adjust the width of the inner frame:

def onCanvasConfigure(self, event):
    self.canvas.itemconfigure("self.viewPort", width=event.width)