简体   繁体   中英

Tkinter scrollbar works only with create_window

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM