1

I am currently struggling using a Tkinter canvas within a scroll frame. By scroll frame I mean a Frame-Canvas structure given in the answer by Gonzo to another thread (Python Tkinter scrollbar for frame). I have adapted that concept and added the horizontal scroll bar. Now, on top of the interior frame I want to place a canvas that has a given minimum size. In the case this minimum size is smaller than the interior frame (or the entire window), there is no problem (since there is no scrolling necessary). However, if I resize the window (and hence the frame) to a value smaller than the canvas (which is the condition under which the scroll bars should be useful), the canvas is not scretched to the required size (in the example below, not to width=500px) rather it is truncated at the smaller size of the frame (width=400px). The scroll bars are active and they scroll the interior frame which is still at 500px.

Below I give you a few more details on the code and problem.

Thank you for looking into it. I appreciate any help on this problem, maybe I'm just doing something obviously wrong.

Cheers, C.

ScrollFrame class (stored in scrollframe.py):

from Tkinter import *

class ScrollFrame(Frame):
    def __init__(self, parent, yscroll=True, xscroll=True, *args, **kw):
        Frame.__init__(self, parent, *args, **kw)
        self.canvas = Canvas(self, bd=0, highlightthickness=0)
        if xscroll:
            hscrollbar = Scrollbar(self, orient=HORIZONTAL)
            hscrollbar.pack(fill=X, side=BOTTOM, expand=FALSE)
            self.canvas.config(xscrollcommand=hscrollbar.set)
            hscrollbar.config(command=self.canvas.xview)
        if yscroll:
            vscrollbar = Scrollbar(self, orient=VERTICAL)
            vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
            self.canvas.config(yscrollcommand=vscrollbar.set)
            vscrollbar.config(command=self.canvas.yview)
        self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)

        # reset the view
        self.canvas.xview_moveto(0)
        self.canvas.yview_moveto(0)

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = Frame(self.canvas, bg="blue")
        self.interior_id = self.canvas.create_window(0, 0, window=interior, anchor=NW)

        # track changes to the canvas and frame width and sync them,
        # also updating the scrollbar
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
            print "interior req: ",size,(interior.winfo_width(),interior.winfo_height())
            self.canvas.config(scrollregion="0 0 %s %s" % size)
            if interior.winfo_reqwidth() != self.canvas.winfo_width() or interior.winfo_reqheight() != sel    f.canvas.winfo_height():
                # update the canvas's width to fit the inner frame
                self.canvas.config(width=interior.winfo_reqwidth(), height=interior.winfo_reqheight(),)
        interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(event):
            if interior.winfo_reqwidth() != self.canvas.winfo_width() or interior.winfo_reqheight() != sel    f.canvas.winfo_height():
                # update the inner frame's width to fill the canvas
                self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width(), height=self.c    anvas.winfo_height())
        self.canvas.bind('<Configure>', _configure_canvas)

The main code: from Tkinter import * from scrollframe import *

root = Tk()
root.geometry("400x300")

frame = Frame(root, bg="blue")
frame.pack(expand=True, fill=BOTH)

sf = ScrollFrame(frame, bg="yellow")
sf.pack(expand=True, fill=BOTH)

sf.interior.config(width=500, height=400)
canvas = Canvas(sf.interior, width=500, height=400, bg="green")
canvas.pack(expand=True, fill=BOTH, side="top")

root.mainloop()

When running, it looks like this: 1. canvas at top left corner (not yet scrolled) 2. canvas at bottom right corner (after scrolling)

The problem: the green canvas should be the requested size (500/400) but it appears to be the size of the grandmother frame where the scrollbars are attached to (400/300) rather than the interior frame, which is the one that is scrolled (500/400) and which is the mother frame of the canvas.

j_4321
  • 15,431
  • 3
  • 34
  • 61
conni
  • 67
  • 6

1 Answers1

1

The _configure_canvas method is the source of your issue. It resizes self.interior frame to the width of self.canvas and since canvas has been packed with the options expand=True, fill=BOTH, it is resized to 400x300.

In addition, this function is not necessary to obtain a scroll frame, the only thing that is needed is to resize self.canvas scrollregion each time self.interior is resized.

Here is the modified code:

from Tkinter import *

class ScrollFrame(Frame):
    def __init__(self, parent, yscroll=True, xscroll=True, *args, **kw):
        Frame.__init__(self, parent, *args, **kw)
        self.canvas = Canvas(self, bd=0, highlightthickness=0)
        if xscroll:
            hscrollbar = Scrollbar(self, orient=HORIZONTAL)
            hscrollbar.pack(fill=X, side=BOTTOM, expand=FALSE)
            self.canvas.config(xscrollcommand=hscrollbar.set)
            hscrollbar.config(command=self.canvas.xview)
        if yscroll:
            vscrollbar = Scrollbar(self, orient=VERTICAL)
            vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
            self.canvas.config(yscrollcommand=vscrollbar.set)
            vscrollbar.config(command=self.canvas.yview)
        self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)

        # reset the view
        self.canvas.xview_moveto(0)
        self.canvas.yview_moveto(0)

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = Frame(self.canvas, bg="blue")
        self.interior_id = self.canvas.create_window(0, 0, window=interior, anchor=NW)

        # track changes to the frame width and update the scrollregion
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            self.canvas.config(scrollregion=self.canvas.bbox('all'))
        interior.bind('<Configure>', _configure_interior)

root = Tk()
root.geometry("400x300")

frame = Frame(root, bg="blue")
frame.pack(expand=True, fill=BOTH)

sf = ScrollFrame(frame, bg="yellow")
sf.pack(expand=True, fill=BOTH)

sf.interior.config(width=500, height=400)
canvas = Canvas(sf.interior, width=500, height=400, bg="green")
canvas.pack(expand=True, fill=BOTH, side="top")

root.mainloop()
j_4321
  • 15,431
  • 3
  • 34
  • 61