Создание скроллбаров / tkinter 6

Скачайте код уроков с 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.

Максим
Я создал этот блог в 2018 году, чтобы распространять полезные учебные материалы, документации и уроки на русском. На сайте опубликовано множество статей по основам python и библиотекам, уроков для начинающих и примеров написания программ.
Мои контакты: Почта
admin@pythonru.comAlex Zabrodin2018-10-26OnlinePython, Programming, HTML, CSS, JavaScript