Вопрос-Ответ

Adding a scrollbar to a group of widgets in Tkinter

Добавление полосы прокрутки в группу виджетов в Tkinter

Я использую Python для синтаксического анализа записей из файла журнала и отображения содержимого записей с помощью Tkinter, и до сих пор это было превосходно. На выходе получается сетка виджетов меток, но иногда строк больше, чем может быть отображено на экране. Я хотел бы добавить полосу прокрутки, которая выглядит так, как будто это должно быть очень просто, но я не могу в этом разобраться.

Документация подразумевает, что только виджеты списка, текстового поля, холста и ввода поддерживают интерфейс полосы прокрутки. Ни один из них не подходит для отображения сетки виджетов. В виджет Canvas можно поместить произвольные виджеты, но, похоже, вам приходится использовать абсолютные координаты, поэтому я не смогу использовать диспетчер компоновки сетки?

Я пытался поместить сетку виджетов во фрейм, но, похоже, это не поддерживает интерфейс полосы прокрутки, поэтому это не работает:

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

Кто-нибудь может предложить способ обойти это ограничение? Мне бы не хотелось переписывать в PyQt и так сильно увеличивать размер моего исполняемого изображения, просто чтобы добавить полосу прокрутки!

Переведено автоматически
Ответ 1

Обзор

Вы можете связать полосы прокрутки только с несколькими виджетами, а корневой виджет и Frame не являются частью этой группы виджетов.

Для этого есть как минимум пара способов. Если вам нужна простая вертикальная или горизонтальная группа виджетов, вы можете использовать текстовый виджет и window_create метод добавления виджетов. Этот метод прост, но не допускает сложной компоновки виджетов.

Более распространенным решением общего назначения является создание виджета canvas и сопоставление полос прокрутки с этим виджетом. Затем в этот холст вставьте рамку, содержащую ваши виджеты label. Определите ширину / высоту рамки и укажите это в параметре canvas scrollregion, чтобы область прокрутки точно соответствовала размеру рамки.

Зачем помещать виджеты во фрейм, а не непосредственно в холст? Полоса прокрутки, прикрепленная к холсту, может прокручивать только элементы, созданные одним из create_ методов. Вы не можете прокручивать элементы, добавленные на холст, с помощью pack, place или grid. Используя фрейм, вы можете использовать эти методы внутри фрейма, а затем вызвать 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

Сделайте ее прокручиваемой

Используйте этот удобный класс, чтобы сделать рамку, содержащую ваши виджеты, прокручиваемой. Выполните следующие действия:


  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") \
.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()
python tkinter