Скачайте код уроков с GitLab: https://gitlab.com/PythonRu/tkinter-uroki
В Tkinter geometry manager занимают все необходимое место в родительском контейнере для размещения виджетов. Но если у этого контейнера фиксированный размер или же он превышает размеры экрана, то появляется область, которая не будет видна пользователям.
Скроллбары в Tkinter автоматически не добавляются, поэтому их нужно создать и добавить как и любой другой виджет. Также нужно учесть, что лишь у некоторых классов виджетов есть параметры, с помощью которых к ним можно добавить скроллбары.
Чтобы это обойти, можно воспользоваться преимуществами виджета Canvas, который позволяет добавить скроллинг в любой контейнер.
Для демонстрации совместной работы классов Canvas
и Scrollbar
с целью создания изменяемого фрейма со скроллбарами, создадим приложение, которое будет динамически менять размер за счет загрузки изображения.
После нажатия на кнопку «Загрузить изображение» сама она пропадает, а в Canvas
загружается изображение, которое больше контейнера. Это может быть любой графический файл.
Это активирует горизонтальный и вертикальный скроллбары, которые будут автоматически подстраиваться при изменении основного окна:
У виджета Canvas есть стандартный интерфейс скроллинга, а также метод create_window()
. Важно обратить внимание на то, что этот скрипт предполагает размещение файла python.gif
в той же директории:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.scroll_x = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.scroll_y = tk.Scrollbar(self, orient=tk.VERTICAL)
self.canvas = tk.Canvas(self, width=300, height=100,
xscrollcommand=self.scroll_x.set,
yscrollcommand=self.scroll_y.set)
self.scroll_x.config(command=self.canvas.xview)
self.scroll_y.config(command=self.canvas.yview)
self.frame = tk.Frame(self.canvas)
self.btn = tk.Button(self.frame, text="Загрузить изображение",
command=self.load_image)
self.btn.pack()
self.canvas.create_window((0, 0), window=self.frame,
anchor=tk.N + tk.W)
self.canvas.grid(row=0, column=0, sticky="nswe")
self.scroll_x.grid(row=1, column=0, sticky="we")
self.scroll_y.grid(row=0, column=1, sticky="ns")
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.bind("", self.resize)
self.update_idletasks()
self.minsize(self.winfo_width(), self.winfo_height())
def resize(self, event):
region = self.canvas.bbox(tk.ALL)
self.canvas.configure(scrollregion=region)
def load_image(self):
self.btn.destroy()
self.image = tk.PhotoImage(file="python.gif")
tk.Label(self.frame, image=self.image).pack()
if __name__ == "__main__":
app = App()
app.mainloop()
Как работают скроллбары в Tkinter
Первые строчки приложения создают скроллбары и присоединяют их к объекту Canvas
с помощью параметров xscrollcommand
и yscrollcommand
, которые ссылаются на метод set()
объектов scroll_x
и scroll_y
соответственно. Этот метод отвечает за перемещение слайдера.
Также нужно настроить параметр command
каждого из скроллбаров после определения Canvas
:
self.scroll_x = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.scroll_y = tk.Scrollbar(self, orient=tk.VERTICAL)
self.canvas = tk.Canvas(self, width=300, height=100,
xscrollcommand=self.scroll_x.set,
yscrollcommand=self.scroll_y.set)
self.scroll_x.config(command=self.canvas.xview)
self.scroll_y.config(command=self.canvas.yview)
Есть возможность сначала создать Canvas
и настроить его параметры позже, когда уже будут созданы экземпляры скроллбаров.
Следующий шаг — добавить фрейм с помощью метода create_window()
. Первый аргумент — положение, где нужно разместить виджет, который в свою очередь передается в аргументе window
. Поскольку оси x
и y
виджета размещаются в верхнем левом углу, разместим виджет в положении (0, 0) и выровняем его в этом углу с помощью anchor=tk.NW
(северо-запад):
self.frame = tk.Frame(self.canvas)
# ...
self.canvas.create_window((0, 0), window=self.frame, anchor=tk.NW)
Затем зададим переменный размер для первых строки и колонки с помощью методов rowconfigure()
и columnconfigure()
. Параметр weight
обозначает относительную ширину, для распределения дополнительного пространства. Однако в этом примере нет колонок или рядков для изменения размера.
Связывание с событием <Configure>
поможет правильно перенастроить Canvas
, когда размер основного окна меняется. Обработка такого типа события работает по тому же принципу, что и события мыши и клавиатуры:
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.bind("<Configure>", self.resize)
В итоге задаем минимальный размер основного окна с текущими шириной и высотой, которые можно получить с помощью методов winfo_width()
или winfo_height()
.
Для получения реального размера контейнера нужно сделать так, чтобы geometry manager прорисовывал все дочерние виджеты в первую очередь с помощью вызова update_idletasks()
. Этот виджет доступен во всех классах виджета и он отвечает за то, чтобы Tkinter обработал все события в процессе ожидания: например, перерисовку или новые вычисления размеров:
self.update_idletasks()
self.minsize(self.winfo_width(), self.winfo_height())
Метод resize
обрабатывает событие изменения размера окна и обновляет параметр scrollregion
, определяющий область Canvas
, которую можно скроллить. Чтобы провести вычисления заново, можно использовать метода bbox()
с константой ALL
. Он возвращает окружающий размер всего виджета Canvas:
def resize(self, event):
region = self.canvas.bbox(tk.ALL)
self.canvas.configure(scrollregion=region)
Tkinter автоматически вызывает несколько событий <Configure>
при старте приложения, поэтому нет необходимости вызывать self.resize()
в конце метода __init__
.
Лишь несколько классов виджетов поддерживают стандартные параметры скроллинга: Listbox
, Text
и Canvas
поддерживают xscrollcommand
и yscrollcommand
, а Entry — только xscrollcommand
. На примере было разобрано, как использовать этот паттерн с Canvas
, поскольку это может быть общее решение, но та же структура применима для любых виджетов.
Также нужно отметить, что в данном случае не вызывался geometry manager для прорисовки кадра, поскольку create_window()
делает это автоматически. Для лучшей организации класса приложения, можно переместить всю функциональность фрейма и внутренние виджеты в отдельный подкласс Frame
.