简体   繁体   English

使用网格管理器添加小部件后的 tkinter 可滚动画布

[英]tkinter scrollable canvas after adding widgets with grid manager

I'm trying to create a canvas widget with a number of widgets embedded within it.我正在尝试创建一个画布小部件,其中嵌入了许多小部件。 Since there will frequently be too many widgets to fit in the vertical space I have for the canvas, it'll need to be scrollable.由于经常有太多的小部件无法容纳我为画布提供的垂直空间,因此它需要可滚动。

import tkinter as tk                # for general gui
import tkinter.ttk as ttk           # for notebook (tabs)

class instructionGeneratorApp():

    def __init__(self, master):

        # create a frame for the canvas and scrollbar
        domainFrame = tk.LabelFrame(master)
        domainFrame.pack(fill=tk.BOTH, expand=1)

        # make the canvas expand before the scrollbar
        domainFrame.rowconfigure(0,weight=1)
        domainFrame.columnconfigure(0,weight=1)

        vertBar = ttk.Scrollbar(domainFrame)
        vertBar.grid(row=0, column=1, sticky=tk.N + tk.S)

        configGridCanvas = tk.Canvas(domainFrame,
                                    bd=0,
                                    yscrollcommand=vertBar.set)
        configGridCanvas.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)

        vertBar.config(command=configGridCanvas.yview)

        # add widgets to canvas

        l = tk.Label(configGridCanvas, text='Products')
        l.grid(row=1, column=0)

        r = 2
        for product in ['Product1','Product2','Product3','Product4','Product5','Product6','Product7','Product8','Product9','Product10','Product11','Product12','Product13','Product14','Product15','Product16','Product17','Product18','Product19','Product20']:
            l = tk.Label(configGridCanvas, text=product)
            l.grid(row=r, column=0)
            c = tk.Checkbutton(configGridCanvas)
            c.grid(row=r, column=1)
            r += 1

        ButtonFrame = tk.Frame(domainFrame)
        ButtonFrame.grid(row=r, column=0)

        removeServerButton = tk.Button(ButtonFrame, text='Remove server')
        removeServerButton.grid(row=0, column=0)

        # set scroll region to bounding box?
        configGridCanvas.config(scrollregion=configGridCanvas.bbox(tk.ALL))


root = tk.Tk()
mainApp = instructionGeneratorApp(root)

root.mainloop()

As best as I can tell, I'm following the effbot pattern for canvas scrollbars , but I end up with either a scrollbar that isn't bound to the canvas, or a canvas that is extending beyond the edges of its master frame:尽我所知,我正在遵循画布滚动条effbot 模式,但我最终得到了一个未绑定到画布的滚动条,或者一个超出其主框架边缘的画布:

应用程序截图 应用程序的较小屏幕截图

I've attempted the solutions on these questions, but there's still something I'm missing:我已经尝试了这些问题的解决方案,但我仍然缺少一些东西:

resizeable scrollable canvas with tkinter 带有 tkinter 的可调整大小的可滚动画布

Tkinter, canvas unable to scroll Tkinter,画布无法滚动

Any idea what I'm doing wrong?知道我做错了什么吗?

I have added some comments to @The Pinapple 's solution for future reference.我在@The Pinapple 的解决方案中添加了一些评论以供将来参考。

from tkinter import *


class ProductItem(Frame):
    def __init__(self, master, message, **kwds):
        Frame.__init__(self, master, **kwds)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.text = Label(self, text=message, anchor='w')
        self.text.grid(row=0, column=0, sticky='nsew')
        self.check = Checkbutton(self, anchor='w')
        self.check.grid(row=0, column=1)


class ScrollableContainer(Frame):
    def __init__(self, master, **kwargs):
        #our scrollable container is a frame, this frame holds the canvas we draw our widgets on
        Frame.__init__(self, master, **kwargs)
        #grid and rowconfigure with weight 1 are used for the scrollablecontainer to utilize the full size it can get from its parent  
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        #canvas and scrollbars are positioned inside the scrollablecontainer frame
        #the scrollbars take a command parameter which is used to position our view on the canvas
        self.canvas = Canvas(self, bd=0, highlightthickness=0)
        self.hScroll = Scrollbar(self, orient='horizontal',
                                 command=self.canvas.xview)
        self.hScroll.grid(row=1, column=0, sticky='we')
        self.vScroll = Scrollbar(self, orient='vertical',
                                 command=self.canvas.yview)
        self.vScroll.grid(row=0, column=1, sticky='ns')
        self.canvas.grid(row=0, column=0, sticky='nsew')
        #We do not only need a command to position but also one to scroll
        self.canvas.configure(xscrollcommand=self.hScroll.set,
                              yscrollcommand=self.vScroll.set)

        #This is the frame where the magic happens, all of our widgets that are needed to be scrollable will be positioned here
        self.frame = Frame(self.canvas, bd=2)
        self.frame.grid_columnconfigure(0, weight=1)

        #A canvas itself is blank, we must tell the canvas to create a window with self.frame as content, anchor=nw means it will be positioned on the upper left corner
        self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')

        self.product_label = Label(self.frame, text='Products')
        self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
        self.products = []
        for i in range(1, 21):
            item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
            item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
            self.products.append(item)

        self.button_frame = Frame(self.frame)
        self.button_frame.grid(row=21, column=0)

        self.remove_server_button = Button(self.button_frame, text='Remove server')
        self.remove_server_button.grid(row=0, column=0)


        self.update_layout()
        #If the widgets inside the canvas / the canvas itself change size,
        #the <Configure> event is fired which passes its new width and height to the corresponding callback
        self.canvas.bind('<Configure>', self.on_configure)

    def update_layout(self):
        #All pending events, callbacks, etc. are processed in a non-blocking manner
        self.frame.update_idletasks()
        #We reconfigure the canvas' scrollregion to fit all of its widgets
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))
        #reset the scroll
        self.canvas.yview('moveto', '1.0')
        #fit the frame to the size of its inner widgets (grid_size)
        self.size = self.frame.grid_size()

    def on_configure(self, event):
        w, h = event.width, event.height
        natural = self.frame.winfo_reqwidth() #natural width of the inner frame
        #If the canvas changes size, we fit the inner frame to its size
        self.canvas.itemconfigure('inner', width=w if w > natural else natural)
        #dont forget to fit the scrollregion, otherwise the scrollbar might behave strange
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))


if __name__ == "__main__":
    root = Tk()
    root.grid_rowconfigure(0, weight=1)
    root.grid_columnconfigure(0, weight=1)
    sc = ScrollableContainer(root, bd=2)
    sc.grid(row=0, column=0, sticky='nsew')

    root.mainloop()

From what I can tell you are looking for something like this..据我所知,您正在寻找这样的东西..

from tkinter import *


class ProductItem(Frame):
    def __init__(self, master, message, **kwds):
        Frame.__init__(self, master, **kwds)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.text = Label(self, text=message, anchor='w')
        self.text.grid(row=0, column=0, sticky='nsew')
        self.check = Checkbutton(self, anchor='w')
        self.check.grid(row=0, column=1)


class ScrollableContainer(Frame):
    def __init__(self, master, **kwargs):
        Frame.__init__(self, master, **kwargs)  # holds canvas & scrollbars
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.canvas = Canvas(self, bd=0, highlightthickness=0)
        self.hScroll = Scrollbar(self, orient='horizontal',
                                 command=self.canvas.xview)
        self.hScroll.grid(row=1, column=0, sticky='we')
        self.vScroll = Scrollbar(self, orient='vertical',
                                 command=self.canvas.yview)
        self.vScroll.grid(row=0, column=1, sticky='ns')
        self.canvas.grid(row=0, column=0, sticky='nsew')
        self.canvas.configure(xscrollcommand=self.hScroll.set,
                              yscrollcommand=self.vScroll.set)

        self.frame = Frame(self.canvas, bd=2)
        self.frame.grid_columnconfigure(0, weight=1)

        self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')

        self.product_label = Label(self.frame, text='Products')
        self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
        self.products = []
        for i in range(1, 21):
            item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
            item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
            self.products.append(item)

        self.button_frame = Frame(self.frame)
        self.button_frame.grid(row=21, column=0)

        self.remove_server_button = Button(self.button_frame, text='Remove server')
        self.remove_server_button.grid(row=0, column=0)

        self.update_layout()
        self.canvas.bind('<Configure>', self.on_configure)

    def update_layout(self):
        self.frame.update_idletasks()
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))
        self.canvas.yview('moveto', '1.0')
        self.size = self.frame.grid_size()

    def on_configure(self, event):
        w, h = event.width, event.height
        natural = self.frame.winfo_reqwidth()
        self.canvas.itemconfigure('inner', width=w if w > natural else natural)
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))


if __name__ == "__main__":
    root = Tk()
    root.grid_rowconfigure(0, weight=1)
    root.grid_columnconfigure(0, weight=1)
    sc = ScrollableContainer(root, bd=2)
    sc.grid(row=0, column=0, sticky='nsew')

    root.mainloop()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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