简体   繁体   English

为Tkinter中的一组小部件添加滚动条

[英]Adding a scrollbar to a group of widgets in Tkinter

I am using Python to parse entries from a log file, and display the entry contents using Tkinter and so far it's been excellent.我正在使用 Python 来解析日志文件中的条目,并使用 Tkinter 显示条目内容,到目前为止一切都很好。 The output is a grid of label widgets, but sometimes there are more rows than can be displayed on the screen. output 是 label 小部件的网格,但有时行数多于屏幕上可以显示的行数。 I'd like to add a scrollbar, which looks like it should be very easy, but I can't figure it out.我想加一个滚动条,看起来应该很容易,但我想不通。

The documentation implies that only the List, Textbox, Canvas and Entry widgets support the scrollbar interface.文档暗示只有列表、文本框、Canvas 和条目小部件支持滚动条界面。 None of these appear to be suitable for displaying a grid of widgets.这些似乎都不适合显示小部件网格。 It's possible to put arbitrary widgets in a Canvas widget, but you appear to have to use absolute co-ordinates, so I wouldn't be able to use the grid layout manager?可以在 Canvas 小部件中放置任意小部件,但您似乎必须使用绝对坐标,所以我无法使用网格布局管理器?

I've tried putting the widget grid into a Frame, but that doesn't seem to support the scrollbar interface, so this doesn't work:我试过将小部件网格放入框架中,但这似乎不支持滚动条界面,所以这不起作用:

mainframe = Frame(root, yscrollcommand=scrollbar.set)

Can anyone suggest a way round this limitation?任何人都可以提出解决此限制的方法吗? I'd hate to have to rewrite in PyQt and increase my executable image size by so much, just to add a scrollbar!我不想不得不在 PyQt 中重写并将我的可执行图像大小增加这么多,只是为了添加一个滚动条!

Overview概述<\/h2>

You can only associate scrollbars with a few widgets, and the root widget and Frame<\/code> aren't part of that group of widgets.您只能将滚动条与一些小部件相关联,并且根小部件和Frame<\/code>不属于该小部件组。

There are at least a couple of ways to do this.至少有几种方法可以做到这一点。 If you need a simple vertical or horizontal group of widgets, you can use a text widget and the window_create<\/code> method to add widgets.如果您需要一组简单的垂直或水平小部件,您可以使用文本小部件和window_create<\/code>方法来添加小部件。 This method is simple, but doesn't allow for a complex layout of the widgets.这种方法很简单,但不允许小部件的复杂布局。

A more common general-purpose solution is to create a canvas widget and associate the scrollbars with that widget.一个更常见的通用解决方案是创建一个画布小部件并将滚动条与该小部件相关联。 Then, into that canvas embed the frame that contains your label widgets.然后,在该画布中嵌入包含标签小部件的框架。 Determine the width\/height of the frame and feed that into the canvas scrollregion<\/code> option so that the scrollregion exactly matches the size of the frame.确定框架的宽度\/高度并将其输入到画布scrollregion<\/code>选项中,以使滚动区域与框架的大小完全匹配。

Why put the widgets in a frame rather than directly in the canvas?为什么将小部件放在框架中而不是直接放在画布中? A scrollbar attached to a canvas can only scroll items created with one of the create_<\/code> methods.附加到画布的滚动条只能滚动使用create_<\/code>方法之一创建的项目。 You cannot scroll items added to a canvas with pack<\/code> , place<\/code> , or grid<\/code> .您不能使用pack<\/code> 、 place<\/code>或grid<\/code>滚动添加到画布的项目。 By using a frame, you can use those methods inside the frame, and then call create_window<\/code> once for the frame.通过使用框架,您可以在框架内使用这些方法,然后为框架调用一次create_window<\/code> 。

Drawing the text items directly on the canvas isn't very hard, so you might want to reconsider that approach if the frame-embedded-in-a-canvas solution seems too complex.直接在画布上绘制文本项并不难,因此如果框架嵌入在画布中的解决方案看起来过于复杂,您可能需要重新考虑这种方法。 Since you're creating a grid, the coordinates of each text item is going to be very easy to compute, especially if each row is the same height (which it probably is if you're using a single font).由于您正在创建一个网格,因此每个文本项的坐标将非常容易计算,特别是如果每​​一行的高度相同(如果您使用单一字体,则可能是这样)。

For drawing directly on the canvas, just figure out the line height of the font you're using (and there are commands for that).要直接在画布上绘图,只需弄清楚您正在使用的字体的行高(并且有相应的命令)。 Then, each y coordinate is row*(lineheight+spacing)<\/code> .然后,每个 y 坐标为row*(lineheight+spacing)<\/code> 。 The x coordinate will be a fixed number based on the widest item in each column. x 坐标将是基于每列中最宽项的固定数字。 If you give everything a tag for the column it is in, you can adjust the x coordinate and width of all items in a column with a single command.如果您为所有内容为其所在的列指定标签,则可以使用单个命令调整列中所有项目的 x 坐标和宽度。

Object-oriented solution面向对象的解决方案<\/h2>

Here's an example of the frame-embedded-in-canvas solution, using an object-oriented approach:这是使用面向对象方法的框架嵌入画布解决方案的示例:

 import tkinter as tk class Example(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") self.frame = tk.Frame(self.canvas, background="#ffffff") self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.vsb.set) self.vsb.pack(side="right", fill="y") self.canvas.pack(side="left", fill="both", expand=True) self.canvas.create_window((4,4), window=self.frame, anchor="nw", tags="self.frame") self.frame.bind("<Configure>", self.onFrameConfigure) self.populate() def populate(self): '''Put in some fake data''' for row in range(100): tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0) t="this is the second column for row %s" %row tk.Label(self.frame, text=t).grid(row=row, column=1) def onFrameConfigure(self, event): '''Reset the scroll region to encompass the inner frame''' self.canvas.configure(scrollregion=self.canvas.bbox("all")) if __name__ == "__main__": root=tk.Tk() example = Example(root) example.pack(side="top", fill="both", expand=True) root.mainloop()<\/code><\/pre>

Procedural solution程序解决方案<\/h2>

Here is a solution that doesn't use a class:这是一个不使用类的解决方案:

 import tkinter as tk def populate(frame): '''Put in some fake data''' for row in range(100): tk.Label(frame, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0) t="this is the second column for row %s" %row tk.Label(frame, text=t).grid(row=row, column=1) def onFrameConfigure(canvas): '''Reset the scroll region to encompass the inner frame''' canvas.configure(scrollregion=canvas.bbox("all")) root = tk.Tk() canvas = tk.Canvas(root, borderwidth=0, background="#ffffff") frame = tk.Frame(canvas, background="#ffffff") vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview) canvas.configure(yscrollcommand=vsb.set) vsb.pack(side="right", fill="y") canvas.pack(side="left", fill="both", expand=True) canvas.create_window((4,4), window=frame, anchor="nw") frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas)) populate(frame) root.mainloop()<\/code><\/pre>"

Make it scrollable使其可滚动<\/h1>

Use this handy class to make scrollable the frame containing your widgets .使用这个方便的类使包含您的小部件的框架可滚动。 Follow these steps:跟着这些步骤:

  1. create the frame创建框架<\/li>
  2. display it (pack, grid, etc)显示它(包、网格等)<\/li>
  3. make it scrollable使其可滚动<\/li>
  4. add widgets inside it在其中添加小部件<\/li>
  5. call the update() method调用 update() 方法<\/li><\/ol>
    import tkinter as tk from tkinter import ttk class Scrollable(tk.Frame): """ Make a frame scrollable with scrollbar on the right. After adding or removing widgets to the scrollable frame, call the update() method to refresh the scrollable area. """ def __init__(self, frame, width=16): scrollbar = tk.Scrollbar(frame, width=width) scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False) self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.config(command=self.canvas.yview) self.canvas.bind('<Configure>', self.__fill_canvas) # base class initialization tk.Frame.__init__(self, frame) # assign this obj (the inner frame) to the windows item of the canvas self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW) def __fill_canvas(self, event): "Enlarge the windows item to the canvas width" canvas_width = event.width self.canvas.itemconfig(self.windows_item, width = canvas_width) def update(self): "Update the canvas and the scrollregion" self.update_idletasks()<\/code><\/pre>

    self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item)) self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))


    Usage example使用示例<\/h2>
    root = tk.Tk() header = ttk.Frame(root) body = ttk.Frame(root) footer = ttk.Frame(root) header.pack() body.pack() footer.pack() ttk.Label(header, text="The header").pack() ttk.Label(footer, text="The Footer").pack() scrollable_body = Scrollable(body, width=32) for i in range(30): ttk.Button(scrollable_body, text="I'm a button in the scrollable frame").grid() scrollable_body.update() root.mainloop()<\/code><\/pre>"

Extends class tk.Frame to support a scrollable Frame扩展类tk.Frame以支持可滚动的 Frame
This class is independent from the widgets to be scrolled and can be used to replace a standard tk.Frame .此类独立于要滚动的小部件,可用于替换标准tk.Frame

在此处输入图像描述


import tkinter as tk

class ScrollbarFrame(tk.Frame):
    """
    Extends class tk.Frame to support a scrollable Frame 
    This class is independent from the widgets to be scrolled and 
    can be used to replace a standard tk.Frame
    """
    def __init__(self, parent, **kwargs):
        tk.Frame.__init__(self, parent, **kwargs)

        # The Scrollbar, layout to the right
        vsb = tk.Scrollbar(self, orient="vertical")
        vsb.pack(side="right", fill="y")

        # The Canvas which supports the Scrollbar Interface, layout to the left
        self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
        self.canvas.pack(side="left", fill="both", expand=True)

        # Bind the Scrollbar to the self.canvas Scrollbar Interface
        self.canvas.configure(yscrollcommand=vsb.set)
        vsb.configure(command=self.canvas.yview)

        # The Frame to be scrolled, layout into the canvas
        # All widgets to be scrolled have to use this Frame as parent
        self.scrolled_frame = tk.Frame(self.canvas, background=self.canvas.cget('bg'))
        self.canvas.create_window((4, 4), window=self.scrolled_frame, anchor="nw")

        # Configures the scrollregion of the Canvas dynamically
        self.scrolled_frame.bind("<Configure>", self.on_configure)

    def on_configure(self, event):
        """Set the scroll region to encompass the scrolled frame"""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

Usage:用法:

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        sbf = ScrollbarFrame(self)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        sbf.grid(row=0, column=0, sticky='nsew')
        # sbf.pack(side="top", fill="both", expand=True)

        # Some data, layout into the sbf.scrolled_frame
        frame = sbf.scrolled_frame
        for row in range(50):
            text = "%s" % row
            tk.Label(frame, text=text,
                     width=3, borderwidth="1", relief="solid") \
                .grid(row=row, column=0)

            text = "this is the second column for row %s" % row
            tk.Label(frame, text=text,
                     background=sbf.scrolled_frame.cget('bg')) \
                .grid(row=row, column=1)


if __name__ == "__main__":
    App().mainloop()

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

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