python - 使用网格管理器添加小部件后的 tkinter 可滚动 Canvas

标签 python python-3.x canvas tkinter

我正在尝试创建一个 Canvas 小部件,其中嵌入了许多小部件。由于经常会有太多的小部件无法容纳在我为 Canvas 设置的垂直空间中,因此它需要是可滚动的。

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()

据我所知,我正在关注 effbot pattern for canvas scrollbars ,但我最终得到了一个未绑定(bind)到 Canvas 的滚动条,或者一个延伸到其主框架边缘之外的 Canvas :

screenshot of application smaller screenshot of application

我已经尝试了这些问题的解决方案,但仍然缺少一些东西:

resizeable scrollable canvas with tkinter

Tkinter, canvas unable to scroll

知道我做错了什么吗?

最佳答案

我在@The Pineapple 中添加了一些评论“供将来引用的解决方案”。

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()

关于python - 使用网格管理器添加小部件后的 tkinter 可滚动 Canvas ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53383943/

相关文章:

python - 样式,格式化切片运算符

python - 为新语言构建词性标注器

python - 如何获取证书的 SHA1 指纹

python - 如何使用 python simple-salesforce 测试插入/更新是否成功

javascript - 调整 canvas/webgl 的大小以适应屏幕的宽度和高度

javascript - 在 HTML Canvas 中包含文本

python - __getattr__ 在模块上

python - PyArray_SimpleNewFromData

python - 为什么我不能将列表中的元素相乘?

javascript - 如何在 Canvas 绘图应用程序中创建着色图片