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

时间:2010-06-21 14:50:15

标签: python tkinter

我使用Python来解析日志文件中的条目,并使用Tkinter显示条目内容,到目前为止它非常出色。输出是标签小部件的网格,但有时会有比屏幕上显示的行多的行。我想添加一个滚动条,看起来应该很容易,但我无法弄明白。

文档暗示只有List,Textbox,Canvas和Entry小部件支持滚动条界面。这些似乎都不适合显示小部件网格。可以在Canvas小部件中放置任意小部件,但您似乎必须使用绝对坐标,因此我将无法使用网格布局管理器?

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

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

有人能建议绕过这个限制吗?我不想在PyQt中重写并增加我的可执行图像大小,只需要添加一个滚动条!

3 个答案:

答案 0 :(得分:95)

概述

您只能将滚动条与几个小部件相关联,并且根小部件和Frame不属于该组小部件。

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

直接在画布上绘制文本项目并不是很难,因此如果框架嵌入画布解决方案看起来过于复杂,您可能需要重新考虑该方法。由于您正在创建网格,因此每个文本项的坐标将非常容易计算,尤其是如果每行的高度相同(如果您使用的是单个字体,则可能就是这样)。

要直接在画布上绘图,只需计算出您正在使用的字体的行高(并且有相应的命令)。然后,每个y坐标为row*(lineheight+spacing)。 x坐标将是基于每列中最宽项目的固定数字。如果为其所在的列提供标记,则可以使用单个命令调整列中所有项的x坐标和宽度。

面向对象的解决方案

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

import tkinter as tk

class Example(tk.Frame):
    def __init__(self, root):

        tk.Frame.__init__(self, root)
        self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
        self.frame = tk.Frame(self.canvas, background="#ffffff")
        self.vsb = tk.Scrollbar(root, 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(root).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()

注意:要在python 2.x中运行,请在导入语句中使用Tkinter而不是tkinter

答案 1 :(得分:2)

使其可滚动

使用这个方便的类可以滚动包含小部件的框架。请按照以下步骤操作:

  1. 创建框架
  2. 显示它(包,网格等)
  3. 使其可滚动
  4. 在其中添加小部件
  5. 调用update()方法
  6. import tkinter as tk
    from tkinter import ttk
    
    class Scrollable(ttk.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()
            self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
    

    用法示例

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

答案 2 :(得分:1)

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

enter image description here


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