Adding a scrollbar to a group of widgets in Tkinter
Добавление полосы прокрутки в группу виджетов в Tkinter
Я использую Python для синтаксического анализа записей из файла журнала и отображения содержимого записей с помощью Tkinter, и до сих пор это было превосходно. На выходе получается сетка виджетов меток, но иногда строк больше, чем может быть отображено на экране. Я хотел бы добавить полосу прокрутки, которая выглядит так, как будто это должно быть очень просто, но я не могу в этом разобраться.
Документация подразумевает, что только виджеты списка, текстового поля, холста и ввода поддерживают интерфейс полосы прокрутки. Ни один из них не подходит для отображения сетки виджетов. В виджет Canvas можно поместить произвольные виджеты, но, похоже, вам приходится использовать абсолютные координаты, поэтому я не смогу использовать диспетчер компоновки сетки?
Я пытался поместить сетку виджетов во фрейм, но, похоже, это не поддерживает интерфейс полосы прокрутки, поэтому это не работает:
Кто-нибудь может предложить способ обойти это ограничение? Мне бы не хотелось переписывать в PyQt и так сильно увеличивать размер моего исполняемого изображения, просто чтобы добавить полосу прокрутки!
Переведено автоматически
Ответ 1
Обзор
Вы можете связать полосы прокрутки только с несколькими виджетами, а корневой виджет и Frame не являются частью этой группы виджетов.
Для этого есть как минимум пара способов. Если вам нужна простая вертикальная или горизонтальная группа виджетов, вы можете использовать текстовый виджет и window_create метод добавления виджетов. Этот метод прост, но не допускает сложной компоновки виджетов.
Более распространенным решением общего назначения является создание виджета canvas и сопоставление полос прокрутки с этим виджетом. Затем в этот холст вставьте рамку, содержащую ваши виджеты label. Определите ширину / высоту рамки и укажите это в параметре canvas scrollregion, чтобы область прокрутки точно соответствовала размеру рамки.
Зачем помещать виджеты во фрейм, а не непосредственно в холст? Полоса прокрутки, прикрепленная к холсту, может прокручивать только элементы, созданные одним из create_ методов. Вы не можете прокручивать элементы, добавленные на холст, с помощью pack, place или grid. Используя фрейм, вы можете использовать эти методы внутри фрейма, а затем вызвать create_window один раз для фрейма.
Рисовать текстовые элементы непосредственно на холсте не очень сложно, поэтому вы можете пересмотреть этот подход, если решение с рамкой, встроенной в холст, кажется слишком сложным. Поскольку вы создаете сетку, координаты каждого текстового элемента будет очень легко вычислить, особенно если каждая строка имеет одинаковую высоту (что, вероятно, так и есть, если вы используете один шрифт).
Для рисования непосредственно на холсте просто определите высоту строки используемого вами шрифта (для этого есть команды). Тогда каждая координата y равна row*(lineheight+spacing). Координата x будет фиксированным числом, основанным на самом широком элементе в каждом столбце. Если вы присвоите всему тег для столбца, в котором оно находится, вы можете настроить координату x и ширину всех элементов в столбце с помощью одной команды.
Объектно-ориентированное решение
Вот пример решения для фрейма, встроенного в холст, с использованием объектно-ориентированного подхода:
defpopulate(self): '''Put in some fake data''' for row inrange(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)
defonFrameConfigure(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
defpopulate(frame): '''Put in some fake data''' for row inrange(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)
defonFrameConfigure(canvas): '''Reset the scroll region to encompass the inner frame''' canvas.configure(scrollregion=canvas.bbox("all"))
Используйте этот удобный класс, чтобы сделать рамку, содержащую ваши виджеты, прокручиваемой. Выполните следующие действия:
создаем фрейм
отобразить ее (пакет, сетку и т.д.)
сделайте ее прокручиваемой
добавьте в нее виджеты
вызовите метод update()
import tkinter as tk from tkinter import ttk
classScrollable(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. """
for i inrange(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
classScrollbarFrame(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)
defon_configure(self, event): """Set the scroll region to encompass the scrolled frame""" self.canvas.configure(scrollregion=self.canvas.bbox("all"))