在 Tkinter 中向一组小部件添加滚动条
- 2024-11-20 08:43:00
- admin 原创
- 7
问题描述:
我正在使用 Python 解析日志文件中的条目,并使用 Tkinter 显示条目内容,到目前为止效果非常好。输出是标签小部件的网格,但有时行数多于屏幕上可以显示的行数。我想添加一个滚动条,这看起来应该很容易,但我搞不懂。
文档暗示只有 List、Textbox、Canvas 和 Entry 小部件支持滚动条界面。这些小部件似乎都不适合显示小部件网格。可以将任意小部件放入 Canvas 小部件中,但您似乎必须使用绝对坐标,所以我无法使用网格布局管理器?
我尝试将小部件网格放入框架中,但它似乎不支持滚动条界面,因此这不起作用:
mainframe = Frame(root, yscrollcommand=scrollbar.set)
有人能建议一种绕过这个限制的方法吗?我不想为了添加滚动条而不得不在 PyQt 中重写并大幅增加可执行映像的大小!
解决方案 1:
概述
您只能将滚动条与少数小部件关联,而根小部件Frame
不属于该小部件组。
至少有几种方法可以做到这一点。如果您需要一个简单的垂直或水平小部件组,您可以使用文本小部件和window_create
添加小部件的方法。这种方法很简单,但不允许对小部件进行复杂的布局。
更常见的通用解决方案是创建一个画布小部件并将滚动条与该小部件关联。然后,将包含标签小部件的框架嵌入到该画布中。确定框架的宽度/高度并将其输入到画布scrollregion
选项中,以便滚动区域与框架的大小完全匹配。
为什么要将小部件放在框架中而不是直接放在画布中?附加到画布上的滚动条只能滚动使用其中一种方法创建的项目。您无法滚动使用、或create_
添加到画布的项目。通过使用框架,您可以在框架内使用这些方法,然后为框架调用一次。pack
`placegrid
create_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:
使其可滚动
使用这个方便的类可以使包含小部件的框架可滚动。请按照以下步骤操作:
创建框架
显示它(包装、网格等)
使其可滚动
在其中添加小部件
调用 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()
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件