简体   繁体   English

tkinter 中的可滚动框架 class

[英]Scrollable frame class in tkinter

I have implemented my own scrollable frame class in tkinter:我在 tkinter 中实现了我自己的可滚动框架 class:

class scrolledFrame(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.canvas = tk.Canvas(self)
        self.canvas.pack(fill = "both", expand = True, side = "left")
        self.scroll = tk.Scrollbar(self, command = self.canvas.yview)
        self.scroll.pack(side = "right", fill = "y")
        self.canvas.config(yscrollcommand = self.scroll.set)
        self.content = tk.Frame(self.canvas)
        self.content.bind("<Configure>", self.resizeCanvas)
        self.contentWindow = self.canvas.create_window((0,0), window = self.content, anchor = "nw")
        self.content.bind("<Enter>", self.enableScrollCanvas)
        self.content.bind("<Leave>", self.disableScrollCanvas)
    def scrollCanvas(self, event):
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
    def enableScrollCanvas(self, event):
        self.canvas.bind_all("<MouseWheel>", self.scrollCanvas)
    def disableScrollCanvas(self, event):
        self.canvas.unbind_all("<MouseWheel>")
    def resizeCanvas(self, event):
        self.update_idletasks()
        self.canvas.config(scrollregion = self.canvas.bbox("all"))
        self.canvas.itemconfig(self.contentWindow, width = self.canvas.winfo_width())

root = tk.Tk()
exampleFrame = scrolledFrame(root)
exampleFrame.pack()
exampleLabel = tk.Label(exampleFrame.content, text = "I'm in the scrolled frame!")
exampleLabel.pack()
root.mainloop()

This works fine, but the problem is to add widgets to the scrolled frame, the parent has to be exampleFrame.content .这工作正常,但问题是将小部件添加到滚动框架,父级必须是exampleFrame.content I have looked at several other examples which all have the same limitation.我查看了其他几个具有相同限制的示例。 Is it possible to configure the class so exampleFrame can be the parent of the widgets instead of exampleFrame.content ?是否可以配置 class 以便exampleFrame可以是小部件的父级而不是exampleFrame.content Thanks in advance.提前致谢。

If you don't mind a little trickery, you can simulate what you want.如果你不介意一点诡计,你可以模拟你想要的。 It's a bit of a hack though.不过,这有点骇人听闻。

The trick is that when you call tk.Frame.__init__ , you need to be giving it the canvas as the parent, making self the content frame.诀窍是,当您调用tk.Frame.__init__时,您需要将 canvas 作为父级,使self成为内容框架。 Of course, to do that you have to create the canvas first, and to create the canvas you have to create the outer frame.当然,要做到这一点,你必须先创建 canvas,然后创建 canvas,你必须创建外框。

It looks something like this:它看起来像这样:

class scrolledFrame(tk.Frame):
    def __init__(self, parent):
        self.outer = tk.Frame(parent)
        self.canvas = tk.Canvas(self.outer)
        self.scroll = tk.Scrollbar(self.outer, command = self.canvas.yview)
        tk.Frame.__init__(self, self.canvas)
        self.contentWindow = self.canvas.create_window((0,0), window = self, anchor = "nw")

However, when you do the above and you try to call pack , place , or grid on the instance of scrolledFrame it's going to do the wrong thing since the instance points to the inner frame rather than the outer frame.但是,当您执行上述操作并尝试在scrolledFrame的实例上调用packplacegrid时,它会做错事,因为实例指向内部框架而不是外部框架。

Here's the trickery: the solution to that is to redirect calls to pack , place , and grid to the outer frame.诡计如下:解决方案是将对packplacegrid的调用重定向到外框。

class scrolledFrame(tk.Frame):
    def __init__(self, parent):
        ...
        self.pack = self.outer.pack
        self.place = self.outer.place
        self.grid = self.outer.grid

With that, you can use scrolledFrame like you want, as long as you use pack , place , or grid when adding it to the layout.这样,您就可以随意使用scrolledFrame ,只要在将其添加到布局时使用packplacegrid

exampleFrame = scrolledFrame(root)
exampleFrame.pack(fill="both", expand=True)
for i in range(100):
    exampleLabel = tk.Label(exampleFrame, text = f"I'm in the scrolled frame! ({i})")
    exampleLabel.pack()

Here's a complete working example, though I've removed the mousewheel code for brevity.这是一个完整的工作示例,尽管为简洁起见,我删除了鼠标滚轮代码。

import tkinter as tk

class scrolledFrame(tk.Frame):
    def __init__(self, parent):
        self.outer = tk.Frame(parent)
        self.canvas = tk.Canvas(self.outer)
        self.scroll = tk.Scrollbar(self.outer, command = self.canvas.yview)
        tk.Frame.__init__(self, self.canvas)
        self.contentWindow = self.canvas.create_window((0,0), window = self, anchor = "nw")

        self.canvas.pack(fill = "both", expand = True, side = "left")
        self.scroll.pack(side = "right", fill = "y")
        self.canvas.config(yscrollcommand = self.scroll.set)
        self.bind("<Configure>", self.resizeCanvas)

        self.pack = self.outer.pack
        self.place = self.outer.place
        self.grid = self.outer.grid

    def resizeCanvas(self, event):
        self.canvas.config(scrollregion = self.canvas.bbox("all"))
        self.canvas.itemconfig(self.contentWindow, width = self.canvas.winfo_width())

root = tk.Tk()
exampleFrame = scrolledFrame(root)
exampleFrame.pack(fill="both", expand=True)
for i in range(100):
    exampleLabel = tk.Label(exampleFrame, text = f"I'm in the scrolled frame! ({i})")
    exampleLabel.pack(fill="x")
root.mainloop()

Thanks to @Novel for their help answering this question.感谢@Novel 帮助回答了这个问题。
The desired behaviour can be achieved by adding a __getattr__ method to the class.可以通过向 class 添加__getattr__方法来实现所需的行为。 This won't work if the class inherits from tk.Frame so that's been removed.如果 class 从tk.Frame继承,这将不起作用,因此已将其删除。 Here's the code:这是代码:

class scrolledFrame():
    def __init__(self, parent):
        self.wrapper = tk.Frame(parent)
        self.canvas = tk.Canvas(self.wrapper)
        self.canvas.pack(fill = "both", expand = True, side = "left")
        self.scroll = tk.Scrollbar(self.wrapper, command = self.canvas.yview)
        self.scroll.pack(side = "right", fill = "y")
        self.canvas.config(yscrollcommand = self.scroll.set)
        self.content = tk.Frame(self.canvas)
        self.content.bind("<Configure>", self.resizeCanvas)
        self.contentWindow = self.canvas.create_window((0,0), window = self.content, anchor = "nw")
        self.content.bind("<Enter>", self.enableScrollCanvas)
        self.content.bind("<Leave>", self.disableScrollCanvas)
        self.attrib = set(dir(tk.Widget))
    def __getattr__(self, item):
        if item in self.attrib:
            return getattr(self.wrapper, item)
        else:
            return getattr(self.content, item)
    def __str__(self):
        return str(self.wrapper)
    def scrollCanvas(self, event):
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
    def enableScrollCanvas(self, event):
        self.canvas.bind_all("<MouseWheel>", self.scrollCanvas)
    def disableScrollCanvas(self, event):
        self.canvas.unbind_all("<MouseWheel>")
    def resizeCanvas(self, event):
        self.update_idletasks()
        self.canvas.config(scrollregion = self.canvas.bbox("all"))
        self.canvas.itemconfig(self.contentWindow, width = self.canvas.winfo_width())
root = tk.Tk()
exampleFrame = scrolledFrame(root)
exampleFrame.pack()
exampleLabel = tk.Label(exampleFrame, text = "I'm in the scrolled frame!")
exampleLabel.pack()
root.mainloop()

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

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