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

2024-11-20 08:43:00
admin
原创
8
摘要:问题描述:我正在使用 Python 解析日志文件中的条目,并使用 Tkinter 显示条目内容,到目前为止效果非常好。输出是标签小部件的网格,但有时行数多于屏幕上可以显示的行数。我想添加一个滚动条,这看起来应该很容易,但我搞不懂。文档暗示只有 List、Textbox、Canvas 和 Entry 小部件支持...

问题描述:

我正在使用 Python 解析日志文件中的条目,并使用 Tkinter 显示条目内容,到目前为止效果非常好。输出是标签小部件的网格,但有时行数多于屏幕上可以显示的行数。我想添加一个滚动条,这看起来应该很容易,但我搞不懂。

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

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

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

有人能建议一种绕过这个限制的方法吗?我不想为了添加滚动条而不得不在 PyQt 中重写并大幅增加可执行映像的大小!


解决方案 1:

概述

您只能将滚动条与少数小部件关联,而根小部件Frame不属于该小部件组。

至少有几种方法可以做到这一点。如果您需要一个简单的垂直或水平小部件组,您可以使用文本小部件和window_create添加小部件的方法。这种方法很简单,但不允许对小部件进行复杂的布局。

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

为什么要将小部件放在框架中而不是直接放在画布中?附加到画布上的滚动条只能滚动使用其中一种方法创建的项目。您无法滚动使用、或create_添加到画布的项目。通过使用框架,您可以在框架内使用这些方法,然后为框架调用一次。pack`placegridcreate_window`

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

若要直接在画布上绘图,只需确定所用字体的行高(有命令可实现)。然后,每个 y 坐标为row*(lineheight+spacing)。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()

程序解决方案

这是一个不使用类的解决方案:

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

解决方案 2:

使其可滚动

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

  1. 创建框架

  2. 显示它(包装、网格等)

  3. 使其可滚动

  4. 在其中添加小部件

  5. 调用 update() 方法


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

解决方案 3:

扩展类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"))

用法:

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") \n                .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')) \n                .grid(row=row, column=1)


if __name__ == "__main__":
    App().mainloop()
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用