I have a panedwindow and, in the right pane, I have a frame in a canvas that I want to scroll. I can get it to scroll only if I call the canvas create_window
function. When I call the create_window
everything works fine, except the content frame does not expand and I have set the grid to sticky=nsew
. In the example code there is a var called x, if x is set to 1 then the frame expands and the scrollbars show up correctly but they don't work, if set to 0 the scrollbars work but the frame does not expand. I need the scrollbars to work if x is set to 1 or 0. You will need to resize the window to see this issue, notice that the separator widget expands to fill the frame but the scrollbars don't scroll anything when x is set to 1.
I'm just about there I have it working 99% just one little glitch. The separator widget does not expand to fill the content frame when the window height is shorter then the content and the window width is wider then content. I have updated the code and added background color to highlight the issue.
import tkinter as tk
import tkinter.ttk as ttk
lorem_ipsum = 'Lorem ipsum dolor sit amet, luctus non. Litora viverra ligula'
class Scrollbar(ttk.Scrollbar):
def __init__(self, parent, canvas, **kwargs):
ttk.Scrollbar.__init__(self, parent, **kwargs)
command = canvas.xview if kwargs.get('orient', tk.VERTICAL) == tk.HORIZONTAL else canvas.yview
self.configure(command=command)
def set(self, low, high):
if float(low) > 0 or float(high) < 1:
self.grid()
else:
self.grid_remove()
ttk.Scrollbar.set(self, low, high)
class App(tk.Tk):
def __init__(self):
super().__init__()
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.title('Paned Window Demo')
self.geometry('420x200')
style = ttk.Style()
style.theme_use('clam')
style.configure('TPanedwindow', background='black')
pw = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
left_frame = ttk.Frame(pw)
right_frame = ttk.Frame(pw)
ttk.Label(left_frame, text='Left Pane').grid()
left_frame.rowconfigure(0, weight=1)
left_frame.columnconfigure(0, weight=1)
left_frame.grid(sticky=tk.NSEW)
right_frame.rowconfigure(0, weight=1)
right_frame.columnconfigure(0, weight=1)
right_frame.grid(sticky=tk.NSEW)
pw.add(left_frame)
pw.add(right_frame)
pw.grid(sticky=tk.NSEW)
canvas = tk.Canvas(right_frame, bg=style.lookup('TFrame', 'background'))
canvas.frame = ttk.Frame(canvas)
canvas.rowconfigure(0, weight=1)
canvas.columnconfigure(0, weight=1)
canvas.grid(sticky=tk.NSEW)
canvas.frame.rowconfigure(990, weight=1)
canvas.frame.columnconfigure(0, weight=1)
canvas.frame.grid(sticky=tk.NSEW)
content = tk.Frame(canvas.frame, bg='blue')
content.rowconfigure(0, weight=1)
content.columnconfigure(0, weight=1)
content.grid(sticky=tk.NSEW)
xscroll = Scrollbar(right_frame, canvas, orient=tk.HORIZONTAL)
yscroll = Scrollbar(right_frame, canvas, orient=tk.VERTICAL)
xscroll.grid(row=990, column=0, sticky=tk.EW)
yscroll.grid(row=0, column=990, sticky=tk.NS)
for idx in range(1, 11):
tk.Label(content, bg='#aaaaaa', fg='#000000', text=f'{idx} {lorem_ipsum}').grid(sticky=tk.NW)
ttk.Separator(content, orient=tk.HORIZONTAL).grid(pady=10, sticky=tk.EW)
for idx in range(11, 21):
tk.Label(content, bg='#aaaaaa', fg='#000000', text=f'{idx} {lorem_ipsum}').grid(sticky=tk.NW)
self.window = canvas.create_window((0, 0), window=canvas.frame, anchor=tk.NW)
self.update_idletasks()
pw.sashpos(0, newpos=100)
def update_canvas(event):
content.update_idletasks()
_, _, width, height = content.bbox(tk.ALL)
if event.width < width or event.height < height:
if not self.window:
self.window = canvas.create_window((0, 0), window=canvas.frame, anchor=tk.NW)
else:
self.window = None
canvas.frame.grid(sticky=tk.NSEW)
canvas.bind('<Configure>', update_canvas)
canvas.configure(scrollregion=content.bbox(tk.ALL))
canvas.configure(xscrollcommand=xscroll.set, yscrollcommand=yscroll.set)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
It is perfectly normal that the scrollbars only work with canvas.create_window()
since this is the adequate method to display a widget inside a canvas so that the canvas is aware of the widget's size. Otherwise you are just using the canvas as a frame. However, the canvas.create_window()
method does not have a sticky
option so you have to manually change the width of the widget when the canvas changes size.
Therefore, in your update_canvas
callback, you need to change the width of the window each time the canvas changes width if the canvas's width is larger than the content's required width:
def update_canvas(event):
# if the new canvas's width (event.width) is larger than the content's
# minimum width (content.winfo_reqwidth()) then make canvas.frame the
# same width as the canvas
if event.width > content.winfo_reqwidth():
canvas.itemconfigure(self.window, width=event.width)
Moreover there are a few useless lines in your code, in particular you don't have to grid canvas.frame
since you display it with canvas.create_window()
, see below:
class App(tk.Tk):
def __init__(self):
super().__init__()
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.title('Paned Window Demo')
self.geometry('420x200')
style = ttk.Style()
style.theme_use('clam')
style.configure('TPanedwindow', background='black')
pw = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
left_frame = ttk.Frame(pw)
right_frame = ttk.Frame(pw)
ttk.Label(left_frame, text='Left Pane').grid()
left_frame.rowconfigure(0, weight=1)
left_frame.columnconfigure(0, weight=1)
left_frame.grid(sticky=tk.NSEW)
right_frame.rowconfigure(0, weight=1)
right_frame.columnconfigure(0, weight=1)
right_frame.grid(sticky=tk.NSEW)
pw.add(left_frame)
pw.add(right_frame)
pw.grid(sticky=tk.NSEW)
canvas = tk.Canvas(right_frame, bg=style.lookup('TFrame', 'background'))
canvas.frame = ttk.Frame(canvas)
# canvas.rowconfigure(0, weight=1) -> useless since no widget will be gridded in the canvas
# canvas.columnconfigure(0, weight=1) -> useless since no widget will be gridded in the canvas
canvas.grid(sticky=tk.NSEW)
canvas.frame.rowconfigure(990, weight=1)
canvas.frame.columnconfigure(0, weight=1)
# canvas.frame.grid(sticky=tk.NSEW) -> no need to grid canvas.frame since it is displayed it with canvas.create_window()
content = tk.Frame(canvas.frame, bg='blue')
content.rowconfigure(0, weight=1)
content.columnconfigure(0, weight=1)
content.grid(sticky=tk.NSEW)
xscroll = Scrollbar(right_frame, canvas, orient=tk.HORIZONTAL)
yscroll = Scrollbar(right_frame, canvas, orient=tk.VERTICAL)
xscroll.grid(row=990, column=0, sticky=tk.EW)
yscroll.grid(row=0, column=990, sticky=tk.NS)
for idx in range(1, 11):
tk.Label(content, bg='#aaaaaa', fg='#000000', text=f'{idx} {lorem_ipsum}').grid(sticky=tk.NW)
ttk.Separator(content, orient=tk.HORIZONTAL).grid(pady=10, sticky=tk.EW)
for idx in range(11, 21):
tk.Label(content, bg='#aaaaaa', fg='#000000', text=f'{idx} {lorem_ipsum}').grid(sticky=tk.NW)
self.window = canvas.create_window((0, 0), window=canvas.frame, anchor=tk.NW)
self.update_idletasks()
pw.sashpos(0, newpos=100)
def update_canvas(event):
# if the new canvas's width (event.width) is larger than the content's
# minimum width (content.winfo_reqwidth()) then make canvas.frame the
# same width as the canvas
if event.width > content.winfo_reqwidth():
canvas.itemconfigure(self.window, width=event.width)
canvas.bind('<Configure>', update_canvas)
canvas.configure(scrollregion=content.bbox(tk.ALL))
canvas.configure(xscrollcommand=xscroll.set, yscrollcommand=yscroll.set)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.