На этом шаге мы рассмотрим подробно работу приложения, приведенного на предыдущем шаге.
Теперь приступим собственно к рассмотрению основных принципов Tkinter-программирования, используя только что написанное приложение в качестве примера, и разберем код нашего приложения построчно.
Ключевой модуль библиотеки Tkinter, в котором определены многие ее ключевые классы, носит название tkinter. Импортируем этот модуль:
import tkinter
Библиотека Tkinter поддерживает два набора элементов управления: старые, определенные в том же модуле tkinter, и более новые, стилизуемые, которые определены в модуле tkinter.ttk. Во вновь создаваемых приложениях рекомендуется применять вторые, поэтому мы импортируем модуль tkinter.ttk:
import tkinter.ttk
Для вывода окна-сообщения нам понадобится модуль tkinter.messagebox:
import tkinter.messagebox
Теперь мы можем приступить к программированию единственного окна нашего приложения: его интерфейса - то есть всех имеющихся в нем элементов управления, и его логики - то есть выполняемых приложением действий.
Библиотека Tkinter использует концепцию контейнера - так в терминологии библиотеки называется элемент интерфейса, служащий для размещения элементов управления, которые составляют или весь интерфейс какого-либо окна, или его относительно независимую часть. Такой контейнер со всеми содержащимися в нем элементами управления помещается в окно.
Итак, определяем класс Application - контейнер, который сформирует содержимое окна нашего приложения:
class Application(tkinter.ttk.Frame):
Как правило, в качестве контейнера используется класс, являющийся производным от класса Frame из модуля tkinter.ttk (отметим, что это новый, стилизуемый контейнер). Frame - это просто серая панель.
Разбор конструктора нашего класса контейнера Application пока оставим на потом. Обратим внимание на метод create_widgets(), который выполняет создание компонентов:
def create_widgets(self):
Все элементы интерфейса, поддерживаемые библиотекой Tkinter, являются компонентами - сущностями, хранящими внутри себя все опции, которые задают их внешний вид и поведение, всю управляющую ими логику, и не зависящими от других компонентов. (В документации по Tk компоненты носят название виджетов.) Компонентами являются кнопки, поля ввода, флажки, переключатели, списки и даже контейнеры.
Каждый компонент с точки зрения программиста представляется классом (что вполне очевидно). Так, компонент кнопки представляется классом Button из модуля tkinter.ttk (это также стилизуемый компонент из нового набора). Следовательно, чтобы создать компонент, достаточно создать экземпляр соответствующего класса. Давайте создадим таким образом кнопку:
self.btnHello = tkinter.ttk.Button(self,
text="Пpивeтcтвoвaть\nпoльзoвaтeля")
Первым параметром в конструкторе класса компонента всегда указывается контейнер, в который должен быть помещен этот компонент. Так, создавая нашу кнопку, мы в первом параметре указали ссылку на наш контейнер, представляемый классом Application.
Настройки, управляющие внешним видом и поведением компонента, представляются опциями. Например, опция text компонента кнопки (Button) задает текст надписи на кнопке.
Отметим, что текст для надписи мы разбили на две строки, использовав специальный символ \n. Практически все компоненты библиотеки Tkinter позволяют это сделать.
Опции можно указать при создании компонента непосредственно в конструкторе его класса с помощью параметров, чьи имена совпадают с названиями соответствующих опций. Например, при создании кнопки мы сразу же задали для нее надпись, присвоив строку с ее текстом параметру text, соответствующему одноименной опции.
Теперь нам нужно сделать так, чтобы при нажатии только что созданной кнопки выполнялся код, выводящий на экран приветствие.
Как только в приложении что-либо происходит - например, пользователь нажимает кнопку мыши или клавишу на клавиатуре, состояние приложение изменяется. В ответ библиотека Tkinter генерирует особую сущность, хранящую все сведения об изменившемся состоянии приложения и называемую событием. Каждое событие представляется экземпляром особого класса, подробнее о котором мы поговорим позже.
Сейчас для нас важно совершенно другое - к любому событию можно привязать какую-либо функцию или метод, который будет вызван после возникновения этого события. Такая функция или метод называется обработчиком. Следовательно, мы может сделать так, чтобы приложение реагировало определенным нами образом на каждое действие пользователя.
Напишем выражение, привязывающее к событию ButtonRelease в качестве обработчика пока еще не определенный метод say_hello() класса Application:
self.btnHello.bind("<ButtonRelease>", self.say_hello)
Событие ButtonRelease возникает при отпускании ранее нажатой кнопки мыши, следовательно, метод say_hello будет вызван после щелчка на кнопке и выведет на экран окно-сообщение.
Можно было бы привязать обработчик к событию Button, которое возникает при нажатии кнопки мыши. Но тогда после выполнения обработчика кнопка останется в нажатом состоянии, и вернуть ее в обычное состояние не представляется возможным. Поэтому в таких случаях лучше обрабатывать событие ButtonRelease.
Вы полагаете, что созданная нами кнопка появится на экране сразу после ее создания? Отнюдь! Нам придется явно вывести ее на экран.
Для вывода любого компонента на экран нужно воспользоваться одним из трех поддерживаемых библиотекой Tkinter диспетчеров компоновки. Такое название носит подсистема, управляющая месторасположением и размерами компонентов, что находятся в контейнере.
Мы воспользуемся диспетчером компоновки Pack, который располагает компоненты вдоль границ контейнера. Вероятно, это самая простая и быстродействующая из подсистем подобного рода. Для этого напишем такое выражение:
self.btnHello.pack()
Метод pack(), вызванный без параметров у нашей кнопки, поместит ее в месторасположение по умолчанию - вдоль верхнего края контейнера-родителя.
Создадим вторую кнопку:
self.btnShow = tkinter.ttk.Button(self)
У первой кнопки мы задали опцию text (то есть надпись) через одноименный параметр конструктора. Но библиотека Tkinter позволяет нам настроить любую опцию компонента уже после его создания. Любой компонент поддерживает функциональность словаря, элементы которого представляют все поддерживаемые компонентом опции. Следовательно, задать надпись для второй кнопки мы можем и так:
self.btnShow["text"] = "Выход"
Обработка щелчков на кнопках - вероятно, одна из наиболее часто выполняемых операций в программировании оконных приложений. Поэтому разработчики библиотеки Tkinter пошли нам, программистам, навстречу, предоставив альтернативный, более простой способ указания функции или метода, который должен выполняться при нажатии кнопки. Такая функция или, как в нашем случае, метод, просто присваивается опции command, поддерживаемой кнопкой:
self.btnShow["command"] = root.destroy
Здесь мы указали метод destroy() класса Tk, чей экземпляр мы создадим позднее и присвоим переменной root. Класс Tk представляет главное окно приложения, а метод destroy() уничтожает это окно и тем самым закрывает приложение.
И не забываем отдать указание диспетчеру компоновки вывести вторую кнопку на экран:
self.btnShow.pack(side="bottom")
Опция side диспетчера компоновки Pack указывает границу родителя, вдоль которой будет расположен выводимый компонент, а ее значение "bottom" задает нижнюю границу.
Теперь сразу же рассмотрим метод say_hello(), который выведет на экран приветствие. Он совсем прост:
def say_hello(self, evt): tkinter.messagebox.showinfo("Test", "Привет, пользователь!")
Обработчик события, неважно - функция это или метод, должен принимать единственным параметром экземпляр класса, представляющий обрабатываемое им событие. В нашем случае это параметр evt. А функция showinfo() из модуля tkinter.messagebox выводит на экран окно-сообщение.
Теперь можно познакомиться с кодом конструктора класса Application:
def __init__(self, master=None): super().__init__(master)
Через параметр master конструктору контейнера передается ссылка на окно, в котором будет размещаться текущий контейнер. Это окно мы передаем конструктору суперкласса при его вызове - иначе у нас ничего не заработает.
Контейнер - это такой же компонент, как, скажем, кнопка. Он не появится в окне, пока мы не дадим на этот счет прямое указание, воспользовавшись диспетчером компоновки. Используем тот же самый Pack, вызвав метод pack() у самого контейнера:
self.pack()
Теперь можно создать компоненты, вызвав рассмотренный нами ранее метод create_widgets():
self.create_widgets()
По-хорошему, надо бы задать заголовок для окна нашего приложения. Сделаем это:
self.master.title("Test")
Экземпляр класса, представляющий окно, мы можем получить из атрибута master, поддерживаемого всеми классами компонентов, а для задания заголовка воспользуемся методом title() класса окна.
И запретим изменение размеров окна - для нашего приложения это ни к чему:
self.master.resizable(False, False)
Метод resizable() класса окна устанавливает возможность изменения размеров окна в виде двух логических величин - соответственно, по горизонтали и вертикали. Значение False запрещает изменение размера.
Так, с классом контейнера Application покончено. Осталось разобрать совсем короткий фрагмент кода, запускающий приложение.
Контейнер, созданный нами, - это лишь содержимое окна, но не само окно. Поэтому нам нужно явно создать окно, в котором будет находиться контейнер со всеми расположенными в нем компонентами.
Библиотека Tkinter поддерживает два типа окон: главное, которое представляет само приложение и при закрытии которого приложение завершается, и вторичное, которое выводится программно, предоставляет дополнительную функциональность и закрытие которого не приведет к завершению работы приложения. Нам нужно главное окно.
Главное окно представляется классом Tk из модуля tkinter. Создадим экземпляр этого класса:
root = tkinter.Tk()
Теперь мы можем вывести в этом окне наш контейнер:
арр = Application(master=root)
Здесь мы создаем экземпляр класса контейнера Application, передав ему в параметре master ссылку на только что подготовленное окно.
Теперь можно запустить приложение:
root.mainloop()
Метод mainloop() класса Tk запускает цикл обработки событий, в процессе которого приложение ожидает события, возникающие в ответ на действия пользователя, и выполняет привязанные к этим событиям обработчики. Это будет продолжаться до тех пор, пока не будет вызван метод destroy() главного окна, в результате чего таковое закроется, завершая тем самым функционирование приложения.
А теперь рассмотрим различные принципы Tkinter-программирования более подробно.
На следующем шаге мы рассмотрим связывание компонентов с данными.