0

I make a custom tkinter widget (ScrollFrame) to make a scrollable frame using the answer from this question: Tkinter scrollbar for frame

Everything is working fine but I have to call the "ConfigureCanvas" function every time new widget is added to ScrollFrame in order to resize the scroll area. Are there any event binds that I could use that would be called when a new widget is packed/grided/placed to the Scroll Frame?

eg:

class ScrollFrame(Frame):
    '''
    '''
    self.packFrame.bind('<NewChildWidgetAdded>', self.ConfigureCanvas)
    '''
    '''

exampleLabel = Label(packFrame, text='Hello')
exampleLabel.pack() # activate the "NewChildWidgetAdded" event?

Here's working code (python 3+). I created a loop that creates 50 labels to give me something to scroll over.

import tkinter
from tkinter import Tk, Frame, filedialog, Button, Listbox, Label, Entry, Text, Canvas, Scrollbar, Radiobutton, Checkbutton, Menu, IntVar, StringVar, BooleanVar, Grid, OptionMenu, Toplevel, ALL, CURRENT, END
#imported more than i needed, copied from other code where this is all used. 

class ScrollFrame(Frame):
    def __init__(self, frame, *args, **kwargs):
        Frame.__init__(self, frame, *args, **kwargs)

        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.vScroll = Scrollbar(self, orient='vertical')
        self.vScroll.grid(row=0, column=1, sticky='wens')
        self.hScroll = Scrollbar(self, orient='horizontal')
        self.hScroll.grid(row=1, column=0, sticky='wens')

        self.canvas = Canvas(self, bd=0, highlightthickness=0, xscrollcommand=self.hScroll.set, yscrollcommand=self.vScroll.set, bg='green')
        self.canvas.grid(row=0, column=0, sticky='wens')
        self.vScroll.config(command=self.canvas.yview)
        self.hScroll.config(command=self.canvas.xview)

        self.packFrame = Frame(self.canvas, bg='blue')
        self.packWindow = self.canvas.create_window(0,0, window=self.packFrame, anchor='nw')

    def ConfigureCanvas(self):
        self.packFrame.update_idletasks()
        size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
        self.canvas.config(scrollregion=(0,0,size[0], size[1]))

mw = Tk()

scrollCavnas = ScrollFrame(mw)
scrollCavnas.pack(side='right', fill='both', expand='yes')
scrollFrame = scrollCavnas.packFrame

temp = 0
while temp < 50:
    Label(scrollFrame, text=temp).grid(row=temp, column=temp)
    temp += 1
    scrollCavnas.ConfigureCanvas() # don't want to have to call this every time a new widget is added.

mw.mainloop()

I tried:

self.packFrame.bind('<map>', self.ConfigureCanvas)

But it looks like this only gets called when the ScrollFrame is created, not when I add a new child widget to the ScrollFrame.

I looked over the documentation (http://tcl.tk/man/tcl8.5/TkCmd/bind.htm#M13) but I didn't notice anything that could do what I wanted.

martineau
  • 119,623
  • 25
  • 170
  • 301
Dave1551
  • 323
  • 2
  • 13
  • Try binding to a `''` event. See [Tkinter Canvas create_window()](https://stackoverflow.com/questions/16820520/tkinter-canvas-create-window). – martineau Apr 10 '19 at 19:19
  • @martineau - This does work but ConfigureCanvas gets called every time the widget gets scrolled. Ideally it would only get called when a new widget is added but this does acomplish what I wanted. – Dave1551 Apr 10 '19 at 19:39
  • Dave1551: Perhaps the event passed to the callback can be examined and unnecessary processing avoided if it's not because something was added. – martineau Apr 10 '19 at 19:48
  • I tried a few things, I added print(event) and print(event.widget) at the top of the ConfgureCanvas function but it would produce the same data regardless if it was called from adding a widget or scrolling the frame. I looked over this list and didn't see anything else that might help. https://infohost.nmt.edu/tcc/help/pubs/tkinter/web/event-handlers.html – Dave1551 Apr 10 '19 at 20:25

1 Answers1

0

One solution is to use the Configure event. The downside is this gets called every time the widget is scrolled.

self.packFrame.bind('<Configure>', self.ConfigureCanvas)

def ConfigureCanvas(self, event=None):
    self.packFrame.update_idletasks()
    size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
    self.canvas.config(scrollregion=(0,0,size[0], size[1]))

I was able to get at least part of the function to only run when adding a new widget. This seems clunky and I believe it will only work if scrolling horizontally (it could be modified to do vertical or both though). This compares the required width to the current scroll region size and if they're different it will resize the scroll region.

def ConfigureCanvas(self, event=None):
    scrollRegion = self.canvas['scrollregion'].split(' ')
    try:
        scrollRegion = int(scrollRegion[2])
    except IndexError:
        scrollRegion = None
    if self.packFrame.winfo_reqwidth() != scrollRegion:
        self.packFrame.update_idletasks()
        size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
        self.canvas.config(scrollregion=(0,0,size[0], size[1]))
Dave1551
  • 323
  • 2
  • 13
  • I found another problem with this method. If you pack another frame in the scroll frame, then the ConfigureCanvas function will be called every time something is added to the child frame. This may or may not cause problems. – Dave1551 Apr 10 '19 at 21:15