class ScrolledFrame(Frame): def __init__(self, parent, xscroll=True, yscroll=True, width=500, height=500, doExtraConfig=True, tipText=None, *args, **kw): Frame.__init__(self, parent, *args, **kw) self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) self.doExtraConfig = doExtraConfig self.xscroll = xscroll self.yscroll = yscroll self.canvas = Tkinter.Canvas(self, borderwidth=0, bg=self.cget('bg'), highlightthickness=0, width=25, height=25) self.canvas.grid(row=0, column=0, sticky=Tkinter.NSEW) if (xscroll): self.xscrollbar = Tkinter.Scrollbar(self, orient=Tkinter.HORIZONTAL, borderwidth=1) self.xscrollbar.config(command=self.canvas.xview) self.canvas.config(xscrollcommand=self.xscrollbar.set) if (yscroll): self.yscrollbar = Tkinter.Scrollbar(self, orient=Tkinter.VERTICAL, borderwidth=1) self.yscrollbar.config(command=self.canvas.yview) self.canvas.config(yscrollcommand=self.yscrollbar.set) # reset the view (always do this if you don't use scrollbars) self.canvas.xview('moveto', 0) self.canvas.yview('moveto', 0) # create the frame self.frame = Frame(self.canvas, tipText=tipText) self.frame.grid(row=0, column=0, sticky=Tkinter.NSEW) self.frame.grid_rowconfigure(0, weight=1) self.frame.grid_columnconfigure(0, weight=1) self.waiting = 0 # track changes to its size self.bind('<Configure>', self.configureCanvasAfter) # track changes to its contents self.frame.bind('<Configure>', self.configureFrame) # place the frame inside the canvas (this also # runs the __configure method) self.canvas.create_window(0, 0, window=self.frame, anchor='nw') #self.frameWidth = self.frame.winfo_reqwidth() #self.frameHeight = self.frame.winfo_reqheight() self.frameWidth = 0 self.frameHeight = 0 def configureFrame(self, event): # frame contents have changed size, but make no change to displayed canvas size if (event.width != self.frameWidth) or (event.height != self.frameHeight): # the size of the frame with widgets in the 'background' self.frameWidth = event.width self.frameHeight = event.height # the scrolled region is always the entire frame self.canvas.config(scrollregion="0 0 %s %s" % (self.frameWidth, self.frameHeight)) self.configureCanvasAfter() def configureCanvasAfter(self, event=None): if self.waiting: return else: self.waiting = 1 self.after_idle(lambda: self.configureCanvas(event)) def configureCanvas(self, event=None): d = 2 * int(self.cget('borderwidth')) if (event): # the canvas visible display size updates with the rezize event unless the axis is not scrolled canvasWidth = event.width - d canvasHeight = event.height - d else: # if no resize event the canvas size is unaltered canvasWidth = int(self.winfo_width()) - d canvasHeight = int(self.winfo_height()) - d if self.xscroll: if (self.frameWidth > 1): if (self.frameWidth > canvasWidth): self.xscrollbar.grid(row=1, column=0, sticky=Tkinter.EW) else: self.xscrollbar.grid_forget() else: self.xscrollbar.grid(row=1, column=0, sticky=Tkinter.EW) else: # display full width canvasWidth = self.frameWidth if self.yscroll: if (self.frameHeight > 1): if (self.frameHeight > canvasHeight): self.yscrollbar.grid(row=0, column=1, sticky=Tkinter.NS) else: self.yscrollbar.grid_forget() else: self.yscrollbar.grid(row=0, column=1, sticky=Tkinter.NS) else: # display full height canvasHeight = self.frameHeight if (self.doExtraConfig): # sometimes below seems to be needed and sometimes not # seems to depend on what else is there in widget besides scrolled frame # can sometimes wildly iterate if below is done self.canvas.config(width=canvasWidth, height=canvasHeight) self.waiting = 0
import Util def config1(event): print 'config1:', frame.winfo_width(), frame.winfo_height() def config2(event): print 'config2:', frame2.winfo_width(), frame2.winfo_height() root = Tkinter.Tk() frame = ScrolledFrame(root) #frame = ScrolledFrame(root, xscroll=false) # below must be pack not grid in order for expansion to work frame.pack(side=Tkinter.TOP, expand=Tkinter.YES, fill=Tkinter.BOTH) frame.bind('<Configure>', config1) frame2 = Frame(frame.frame) frame2.bind('<Configure>', config2) for i in range(30): t = 'a very long label %s' % i label = Tkinter.Label(frame2, text=t) label.grid(sticky=Tkinter.NSEW) frame2.grid(sticky=Tkinter.NSEW) button = Util.createQuitButton(root) button.pack() root.mainloop()