Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tkinter Dynamic scrollbar for a dynamic GUI not updating with GUI

Tags:

python

tkinter

This is related to a previous question: Tkinter dynamically create widgets from button

At the time that I asked the previous question, I believed that it would be easy to add a scrollable frame around the dynamic GUI. Instead, I have had a single problem with the scrollbar not detecting the new frames and entry boxes after the button is pressed. How do I solve this without editing the ScrollFrame class much?

I know that the Scrollbarframe works with other widgets it is just that the dynamic component is causing issues. When I shrink the vertical size of the window past the original location of the createWidgets button, the scrollbar appears, but the scrollbar is not present for the rest of the dynamically created widgets. Does the canvas not detect that the vertical size of the frame increases with a button press?

Note: I am aware that wildcard imports are awful. I'm just using one for the example

from tkinter import *

class AutoScrollbar(Scrollbar):
   # A scrollbar that hides itself if it's not needed.
   # Only works if you use the grid geometry manager!
    def set(self, lo, hi):
        if float(lo) <= 0.0 and float(hi) >= 1.0:
            # grid_remove is currently missing from Tkinter!
            self.tk.call("grid", "remove", self)
        else:
            self.grid()
        Scrollbar.set(self, lo, hi)
    def pack(self, **kw):
        raise TclError("cannot use pack with this widget")
    def place(self, **kw):
        raise TclError("cannot use place with this widget")

class ScrollFrame:
    def __init__(self, master):

        self.vscrollbar = AutoScrollbar(master)
        self.vscrollbar.grid(row=0, column=1, sticky=N+S)
        self.hscrollbar = AutoScrollbar(master, orient=HORIZONTAL)
        self.hscrollbar.grid(row=1, column=0, sticky=E+W)

        self.canvas = Canvas(master, yscrollcommand=self.vscrollbar.set, 
                        xscrollcommand=self.hscrollbar.set)
        self.canvas.grid(row=0, column=0, sticky=N+S+E+W)

        self.vscrollbar.config(command=self.canvas.yview)
        self.hscrollbar.config(command=self.canvas.xview)

        # make the canvas expandable
        master.grid_rowconfigure(0, weight=1)
        master.grid_columnconfigure(0, weight=1)

        # create frame inside canvas
        self.frame = Frame(self.canvas)
        self.frame.rowconfigure(1, weight=1)
        self.frame.columnconfigure(1, weight=1)

    def update(self):
        self.canvas.create_window(0, 0, anchor=NW, window=self.frame)
        self.frame.update_idletasks()
        self.canvas.config(scrollregion=self.canvas.bbox("all"))

        if self.frame.winfo_reqwidth() != self.canvas.winfo_width():
            # update the canvas's width to fit the inner frame
            self.canvas.config(width = self.frame.winfo_reqwidth())
        if self.frame.winfo_reqheight() != self.canvas.winfo_height():
            # update the canvas's width to fit the inner frame
            self.canvas.config(height = self.frame.winfo_reqheight())
frames = []
widgets = []            

def createwidgets():
    global widgetNames
    global frameNames

    frame = Frame(o.frame, borderwidth=2, relief="groove")
    frames.append(frame)

    frame.pack(side="top", fill="x")

    widget = Entry(frame)
    widgets.append(widget)

    widget.pack(side="left")

root = Tk()
o = ScrollFrame(root)
label = Label(o.frame, text = "test")
label1 = Label(o.frame, text = "test")
label2 = Label(o.frame, text = "test")
label3 = Label(o.frame, text = "test")
label.pack()
label1.pack()
label2.pack()
label3.pack()


createWidgetButton = Button(o.frame, text="createWidgets", 
command=createwidgets)
createWidgetButton.pack(side="bottom", fill="x")

o.update()
root.mainloop()

This is what the window would look like if it was fully expanded

If I were to shrink the window, it should immediately create a vertical scrollbar because that would cover a widget. However, the scrollbar acts like the program was still in its initial state.

Incorrect Scrollbar(at the moment that the scrollbar appears)

like image 209
WCTech Avatar asked Oct 21 '25 04:10

WCTech


1 Answers

You need to make sure that you update the canvas scrollregion whenever you add widgets to the inner frame. The most common solution is to bind to the frame's <Configure> event, which will fire whenever the frame changes size.

In ScrollFrame.__init__ add the following line after you create the frame:

self.frame.bind("<Configure>", self.reset_scrollregion)

Then, add this function to ScrollFrame:

def reset_scrollregion(self, event):
    self.canvas.configure(scrollregion=self.canvas.bbox("all")
like image 129
Bryan Oakley Avatar answered Oct 23 '25 19:10

Bryan Oakley