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

Python 向Tkinter中的一组小部件添加滚动条,python,tkinter,tkinter.scrollbar,Python,Tkinter,Tkinter.scrollbar,我正在使用Python解析日志文件中的条目,并使用Tkinter显示条目内容,到目前为止,它非常出色。输出是一个标签小部件的网格,但有时行数超过了屏幕上显示的行数。我想添加一个滚动条,看起来应该很简单,但我想不出来 文档表明,只有列表、文本框、画布和条目小部件支持滚动条界面。所有这些似乎都不适合显示小部件网格。可以在画布小部件中放置任意小部件,但您似乎必须使用绝对坐标,所以我无法使用网格布局管理器 我已经尝试将小部件网格放入一个框架中,但它似乎不支持滚动条界面,因此这不起作用: mainfram

我正在使用Python解析日志文件中的条目,并使用Tkinter显示条目内容,到目前为止,它非常出色。输出是一个标签小部件的网格,但有时行数超过了屏幕上显示的行数。我想添加一个滚动条,看起来应该很简单,但我想不出来

文档表明,只有列表、文本框、画布和条目小部件支持滚动条界面。所有这些似乎都不适合显示小部件网格。可以在画布小部件中放置任意小部件,但您似乎必须使用绝对坐标,所以我无法使用网格布局管理器

我已经尝试将小部件网格放入一个框架中,但它似乎不支持滚动条界面,因此这不起作用:

mainframe = Frame(root, yscrollcommand=scrollbar.set)
有人能提出一种绕过这一限制的方法吗?我不想为了添加一个滚动条而不得不在PyQt中重写并将可执行图像大小增加这么多

概述 您只能将滚动条与一些小部件相关联,并且根小部件和
Frame
不属于该小部件组

最常见的解决方案是创建画布小部件并将滚动条与该小部件关联。然后,在画布中嵌入包含标签小部件的框架。确定帧的宽度/高度,并将其输入画布
scrollregion
选项,以便scrollregion与帧的大小完全匹配

为什么要将小部件放在框架中,而不是直接放在画布中?附着在画布上的滚动条只能滚动使用
create\uu
方法之一创建的项目。您不能使用
pack
place
grid
滚动添加到画布的项目。通过使用帧,您可以在帧内使用这些方法,然后为该帧调用一次
create\u window

直接在画布上绘制文本项并不困难,因此如果frame-embedded-in-a-canvas解决方案看起来太复杂,您可能需要重新考虑这种方法。由于要创建网格,每个文本项的坐标将非常容易计算,特别是如果每行的高度相同(如果使用单一字体,则可能是相同的高度)

对于直接在画布上绘制,只需计算出所使用字体的线条高度(有相应的命令)。然后,每个y坐标都是
行*(线宽+间距)
。x坐标将是基于每列中最宽项目的固定数字。如果为列中的所有项都指定一个标记,则可以使用单个命令调整列中所有项的x坐标和宽度

面向对象解决方案 以下是使用面向对象方法嵌入画布解决方案的框架示例:

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()
将tkinter作为tk导入
类示例(tk.Frame):
定义初始化(自身,父级):
tk.Frame.\uuuu init\uuuuu(自,父)
self.canvas=tk.canvas(self,borderwidth=0,background=“#ffffff”)
self.frame=tk.frame(self.canvas,background=“#ffffffff”)
self.vsb=tk.Scrollbar(self,orient=“vertical”,command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
自包装(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(“,self.onFrameConfigure)
self.populate()
def填充(自我):
''输入一些假数据''
对于范围(100)中的行:
tk.Label(self.frame,text=“%s”%row,width=3,borderwidth=“1”,
relief=“solid”).grid(行=行,列=0)
t=“这是第%s行“%row”的第二列
tk.Label(self.frame,text=t).grid(row=row,column=1)
def onFrameConfigure(自我,事件):
''重置滚动区域以包含内部框架''
self.canvas.configure(scrollregion=self.canvas.bbox(“全部”))
如果名称=“\uuuuu main\uuuuuuuu”:
root=tk.tk()
示例=示例(根)
示例.pack(side=“top”,fill=“both”,expand=True)
root.mainloop()
程序解决方案 下面是一个不使用类的解决方案:

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()
将tkinter作为tk导入
def填充(帧):
''输入一些假数据''
对于范围(100)中的行:
tk.Label(frame,text=“%s”%row,width=3,borderwidth=“1”,
relief=“solid”).grid(行=行,列=0)
t=“这是第%s行“%row”的第二列
tk.Label(frame,text=t).grid(row=row,column=1)
def onFrameConfigure(画布):
''重置滚动区域以包含内部框架''
configure(scrollregion=canvas.bbox(“全部”))
root=tk.tk()
canvas=tk.canvas(根,borderwidth=0,background=“#ffffffff”)
frame=tk.frame(画布,背景=“#ffffff”)
vsb=tk.Scrollbar(root,orient=“vertical”,command=canvas.yview)
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(“”,lambda事件,canvas=canvas:onFrameConfigure(canvas))
填充(框架)
root.mainloop()
使其可滚动 使用这个方便的类使包含小部件的框架可以滚动。遵循以下步骤:

  • 创建框架
  • 显示它(包、网格等)
  • 使其可滚动
  • 在其中添加小部件
  • 调用update()方法


  • 扩展类
    tk.Frame
    以支持可滚动的框架
    此类独立于要滚动的小部件,可用于替换标准的
    tk.Frame



    我现在正在试这个。首先,我只是将数据加载到一个框架中,然后将框架放在画布中,但窗口的大小并没有根据实际情况调整
    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()
    
    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()
    
    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"))
    
    
    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()