While I'm working on a project, I like to code up quick tests at the Python prompt to figure out the details of how to do things I'm not familiar with. Such is the case here. I need a scrollable portion of a window, so after doing some reading, I decided the correct way would be to use a Canvas widget linked to a Scrollbar. It took some trial-and-error as I've never used the Canvas widget before, but I figured it out... ish.
However, when I scroll the Canvas, the Framed widgets on it will overlap and obscure other parts of the interface, including the LabelFrame which is the Canvas' parent. This is most undesirable. I tried various combinations of using the lower()
and lift()
methods of the Canvas, the LabelFrame, and the Frame on the Canvas to remedy this, but none did. I did find that by setting the textwidget
option of the LabelFrame to a Label widget, and calling lift()
on that, it will appear on top of the Canvas widgets; but they'll still obscure the LabelFrame's border and other widgets below the LabelFrame, so that doesn't help much. Here's about what I did in my testing:
import tkinter
win = tkinter.Tk()
outerFrame = tkinter.LabelFrame(win, text="Outer Frame")
outerFrame.grid(sticky=tkinter.NSEW)
outerButton = tkinter.Button(win, text="Button")
outerButton.grid(padx=3, pady=1, sticky=tkinter.EW)
canvasScroll = tkinter.Scrollbar(outerFrame, orient=tkinter.VERTICAL)
canvas = tkinter.Canvas(outerFrame, yscrollcommand=canvasScroll.set, scrollregion=(0,0,0,512))
canvasScroll.configure(command=canvas.yview)
canvas.grid(row=0, column=0, sticky=tkinter.NSEW)
canvasScroll.grid(row=0, column=1, sticky=tkinter.NSEW)
innerFrame = tkinter.LabelFrame(win, text="Inner Frame")
innerLabel = tkinter.Label(innerFrame, text="Label 1")
innerCheckbutton1 = tkinter.Checkbutton(innerFrame, text="Checkbutton 1")
innerCheckbutton2 = tkinter.Checkbutton(innerFrame, text="Checkbutton 2")
innerListbox = tkinter.Listbox(innerFrame)
innerEntry = tkinter.Entry(innerFrame, width=10)
innerButton = tkinter.Button(innerFrame, text="Inner Button")
innerRadiobutton1 = tkinter.Radiobutton(innerFrame, text="Radiobutton 1")
innerRadiobutton2 = tkinter.Radiobutton(innerFrame, text="Radiobutton 2")
innerLabel.grid()
innerCheckbutton1.grid()
innerCheckbutton2.grid()
innerListbox.grid(sticky=tkinter.NSEW)
innerEntry.grid()
innerButton.grid()
innerRadiobutton1.grid()
innerRadiobutton2.grid()
canvas.create_window(0, 0, anchor=tkinter.NW, window=innerFrame)
It demonstrates the issue pretty well. What I want is a way to keep the Canvas widgets in the Canvas' LabelFrame. Is there a simple way to do that?
In a typical situation, when one widget is placed inside another (using master
parameter), the parent clips it, and therefore the part of the child widget that is outside the parent is not visible. Other widgets will not perform clipping for this child, only its master does it.
So when you created innerFrame
as innerFrame = tkinter.LabelFrame(win, text="Inner Frame")
, you set win
to be its master and so it is responsible for clipping. Since the canvas is not a master for innerFrame
, it will not do any clipping and so widgets created with create_window
function will extend beyond the canvas and be visible outside it.
So to make sure everything works as it should, set the master
for the innerFrame
to be canvas
rather than win
.
It can be shown that clipping still works in the following example:
import tkinter
win = tkinter.Tk()
win.configure(bg='green')
win.geometry('1200x600')
clipping_frame = tkinter.LabelFrame(win, bg='purple', text='clipping frame')
clipping_frame.pack(pady=40)
outerFrame = tkinter.LabelFrame(clipping_frame, text="Outer Frame")
outerFrame.grid(padx=70, pady=70)
canvasScroll = tkinter.Scrollbar(outerFrame, orient=tkinter.VERTICAL)
canvas = tkinter.Canvas(outerFrame, yscrollcommand=canvasScroll.set, scrollregion=(0, -100, 0, 712))
canvasScroll.configure(command=canvas.yview)
canvas.grid(row=0, column=0, sticky=tkinter.NSEW)
canvasScroll.grid(row=0, column=1, sticky=tkinter.NSEW)
innerFrame = tkinter.LabelFrame(clipping_frame, text="Inner Frame")
innerLabel = tkinter.Label(innerFrame, text="Label 1")
innerListbox = tkinter.Listbox(innerFrame, height=30)
innerLabel.grid()
innerListbox.grid(sticky=tkinter.NSEW)
canvas.create_window(0, 0, anchor=tkinter.NW, window=innerFrame)
win.mainloop()
You will notice that innerFrame
is clipped by clipping_frame
.
Hope this helps.