Tkinter в Python - библиотека для создания приложений с графическим интерфейсом (программ с GUI) - Академия Selectel

Библиотека Tkinter в Python

Инструкция о работе с библиотекой Tkinter, позволяющая создавать приложения с графическим интерфейсом.

Введение

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

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

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

GUI (графический интерфейс) в Python

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

GUI (Graphical User Interface) — графический интерфейс пользователя, взаимодействие с которым происходит с помощью клавиатуры и мыши. Сейчас тяжело найти программу без графического интерфейса, благодаря чему нет необходимости пользоваться терминалом, управляя приложением через команды.

Для работы с GUI в Python есть и другие библиотеки: Kivy, Python QT, wxPython.

Однако мы рассмотрим именно Tkinter, позволяющий быстро и просто реализовать необходимый интерфейс.

Введение в Tkinter

Tkinter — пакет для языка Python, нужный для работы со средствами Tk. Библиотека Tk написана на языке программирования Tcl и содержит в себе компоненты GUI. 

Написание приложения с Tkinter можно описать с помощью нескольких этапов:

  1. Первоначально необходимо импортировать модуль Tkinter (подключить с помощью директивы import).
  2. Создать основное окно, в котором и будут располагаться все элементы.
  3. Добавить необходимые конкретно вам виджеты – визуальные компоненты графического интерфейса, отвечающие за конкретные функции.
  4. Создать главный цикл событий, включающий в себя все возможные исходы при взаимодействии пользователя с приложением.

Уже можно сделать вывод, что основа приложения на Tkinter – виджеты. Работа с ними может напоминать теги в HTML, позволяющие создавать самые разные элементы.

Всего в библиотеке 18 разных виджетов, которые мы дальше и будем подробно рассматривать.

Идея приложения

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

Проектирование приложения и создание эскиза

Прежде чем приступить непосредственно к написанию кода, нужно продумать, как будет выглядеть приложение, и описать логику работы программы.

Сначала нужно решить, какие данные и в каком виде мы будем получать от пользователя. В случае нашего калькулятора пользователь будет вводить вещественные числа, необходимые для расчета, в специальные поля для ввода. После нажатия на кнопку будет производиться расчет площади, и пользователь увидит результат в интерактивном всплывающем окне.

Схематичный макет будет выглядеть так:

Создание эскиза

Теперь можно приступить к самой реализации.

Импортирование библиотеки

Для реализации этого приложения мы будем использовать версию Python 3.8.

В специализированной для Python среде разработки писать приложение будет удобнее всего. Подсветка синтаксиса и подсказки во время написания кода сильно упростят и ускорят процесс.

Так как Tkinter предустановлен в Python, не нужно его скачивать, достаточно сразу импортировать в проекте:


    import tkinter as tk

После этого нам доступны все модули из этой библиотеки.

Настройка главного окна

После того, как библиотека импортирована, нужно импортировать методы. Загрузим их следующим образом:


    from tkinter import * 
from tkinter import messagebox

Первая строка загружает все методы (обозначаются символом *) из Tkinter и позволяет использовать без ссылки на их наименование. Во второй строке мы импортируем конкретный метод. Это может быть удобно, если мы часто обращаемся к нему.

Далее необходимо создать основное окно приложения. Для этого потребуется модуль Tk:


    window = Tk() 

Главное окно можно назвать и иначе (root или screen). Добавим название приложения:


    window.title("Калькулятор площади треугольника")

С помощью специального метода мы можем явно указать размер окна:


    window.geometry("500x250")

Если выполнить написанный код, то, на первый взгляд, ничего не произойдет. На самом деле окно открылось и сразу же закрылось. Чтобы избежать этого и явно указать, что окно не должно закрываться (пока пользователь не захочет), в конце программы нужно использовать функцию, которая вызывает бесконечный цикл:


    window.mainloop()

Также этот метод отвечает за обработку событий (нажатия на клавиши или кнопки).

Теперь при запуске программы мы увидим окно, ожидающее взаимодействия с пользователем:

Окно взаимодействия

Так приложение будет выглядеть на операционной системе Windows (на macOs и Linux внешний вид будет немного отличаться).

Создание виджетов

Чаще всего окно приложения состоит из нескольких элементов. Например, в нашем приложении обязательно должны быть поля для ввода и кнопка. Важно, чтобы поля не накладывались друг на друга и не уходили за рамки окна. Для того чтобы правильно расположить виджеты, в Tkinter есть специальные методы.

Упаковщики

  • pack предназначается для работы с контейнерами для элементов. С помощью него можно позиционировать содержимое контейнеров.
  • place нужен для размещения объектов с помощью координат.
  • grid размещает элементы в соответствии с ячейками сетки, разделяющей окно приложения.

Для демонстрации работы этих методов, мы воспользуемся несколькими из них.

Frame

Теперь добавим виджет Frame, где будут размещены остальные элементы: выпадающий список, кнопки и другие. Виджеты можно располагать сразу в window, но создание отдельного фрейма предпочтительнее. Для его объявления нужно передать несколько параметров:


    frame = Frame(
   window,
   padx=10,
   pady=10
)
frame.pack(expand=True)

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

Здесь как раз применяется метод pack для позиционирования виджета в окне. С помощью свойства expand=True указывается, что виджет заполняет весь контейнер, выделенный для него.

Еще может быть полезным свойство fill, заставляющее виджет заполнять все доступное пространство:


    frame.pack(fill=Y)

Можно растянуть виджет на все пространство целиком или только по конкретной оси (как в примере, по вертикали).

Атрибут relief

С помощью атрибута relief можно изменить стиль рамки. При добавлении специального значения (FLAT, SUNKEN, RAISED, GROOVE, RIDGE) стиль границы для конкретного фрейма изменится. 

Этот эффект может пригодиться, если нужно как-то стилизовать элементы. Однако для применения эффекта нужно также установить значение атрибута borderwidth, изменяющего ширину рамки в пикселях, выше 1:


    frame = Frame(master=window, relief=SUNKEN, borderwidth=5) 

Добавление основных элементов

Попробуем добавить виджеты. В нашем приложении обязательно будут следующие элементы: надписи (Label), поля для ввода информации (Entry), кнопки (Button). Рассмотрим на практике, как ими пользоваться.

Label

Сначала добавим надпись. Для этого создадим экземпляр встроенного класса Label:


    method_lbl = Label(
   frame,
   text="Выберите способ расчета площади"
)
method_lbl.grid(row=1, column=1)

Мы передаем виджету Label два параметра: фрейм (который мы описали выше) с уже заданными отступами и сам текст.

Также для изменения внешнего вида, можно использовать дополнительные параметры: например, чтобы задать цвет фона или размер шрифта. Еще с помощью Label можно вставить картинку на окно приложения.

Здесь для позиционирования элемента используем метод grid и сразу указываем, что надпись должна располагаться в ячейке с координатами “1 строка, 1 столбец”. Запустив код, мы увидим следующее:

Координаты записи

Важно, что без вызова функции grid() текст не будет отображаться. Элемент располагается в центре окна, так как пока он единственный компонент на экране. Независимо от того, какие координаты мы укажем, надпись будет строго в центре. Это изменится после того, как мы добавим другие компоненты. Тогда объекты будут корректно располагаться относительно друг друга.

Если уже после добавление виджета, мы хотим как-то изменить его параметры (например, в обработчике события), в этом поможет следующий метод:


    method_lbl.configure(text="Не выбирайте")

Combobox

Теперь добавим на экран поле с выпадающим списком. Для этого понадобится виджет Combobox, который необходимо импортировать отдельно:


    from tkinter.ttk import Combobox 

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


    methods = ["Через основание и высоту",
           "Через две стороны и угол"]

Создадим сам объект:


    combobox = Combobox(frame, values=methods, width=30, state="readonly")
combobox.grid(row=2, column=1)
combobox.set("Через основание и высоту")
combobox.bind("<<ComboboxSelected>>", selected)

В аргументы Combobox мы передаем следующее: виджет frame, в котором будет располагаться объект, подготовленный список methods, ширину поля и параметр state со значением readonly, указывающий, что пользователь не сможет редактировать значения выпадающего списка.

Располагаем элемент уже знакомым методом grid. Метод set позволяет обозначить значение, которое будет выбрано по умолчанию.

Запустив приложение, мы получим следующее:

Выбор значения в выпадающем поле

Самое интересное заключается в последней строчке. Так как нужно отрисовывать разные объекты в зависимости от того, какой метод выберет пользователь, нам нужно знать, какое значение он выбрал. Для этого и нужен метод bind с указанными параметрами. А selected – это функция обработки, в которой мы и будем определять, что именно необходимо реализовать.

Рассмотрим возможную реализацию этой функции:


    def selected(event):
    selection = combobox.get()
    if selection == "Через основание и высоту":
        method_with_base_and_height()
    elif selection == "Через две стороны и угол":
        method_with_sides_and_angle()

Обязательным параметром для этой функции является событие event. Уже в теле функции с помощью метода get мы получаем значение, которое выбрал пользователь из выпадающего списка. После чего с помощью условного оператора мы переходим в тот метод, который был выбран.

Entry

Подробно рассмотрим реализацию метода method_with_base_and_height().

Чтобы рассчитать площадь треугольника, используя высоту и основание, добавим поля для ввода этих значений и подписи к ним.


    base_lbl = Label(
    frame,
    text="Основание треугольника"
)
base_lbl.grid(row=3, column=1, pady=10)
base_ent = Entry(
    frame
)
base_ent.grid(row=3, column=2)

С помощью уже знакомого виджета Label мы добавляем подпись для поля и само поле с помощью Entry, для создания которого мы указываем расположение. Также можно задать размер поля используя параметр width.

Метод grid() позволяет расположить новые объекты под выпадающим списком. Вот так это будет выглядеть:

Ввод значения в текстовое поле

Сейчас, чтобы установить фокус на ввод текста, нужно нажать на само поле. Чтобы установить автоматический фокус на конкретное поле, можно использовать функцию focus():


    base_ent.focus()

Теперь при запуске кода, виджет уже будет в фокусе, что позволит сразу написать значение.

Если же для каких-то целей необходимо отключить поле ввода (чтобы в него нельзя было ввести текст), то можно установить для него специальное состояние:


    base_ent = Entry(
    frame,
    state="disabled"
)
base_ent.grid(row=3, column=2)

И выглядеть это будет таким образом:

Отключение поля ввода

Добавим следующий блок объектов: подпись и поле для ввода высоты:


    height_lbl = Label(
    frame,
    text="Высота треугольника"
)
height_lbl.grid(row=4, column=1, pady=10)
height_ent = Entry(
    frame
)
height_ent.grid(row=4, column=2)

Запустив код, мы получим следующий результат:

Подпись и поле для вывода

Кроме get() у Entry еще есть методы delete() и insert() для удаления и вставки текста, соответственно.

Button

Осталось добавить кнопку, по нажатию на которую будет рассчитывать площадь из полученных данных. Для этого воспользуемся виджетом Button:


    calc_btn = Button(
    frame,
    text="Рассчитать площадь",
    command=calculate_area_with_base_and_height(base_ent.get(), height_ent.get())
)
calc_btn.grid(row=5, column=2)

В конструктор нужно передать несколько параметров: месторасположение (то есть frame), текст и команду, которая будет выполняться.

Здесь возникает один неочевидный момент: если оставить конфигурацию кнопки в таком виде, то при запуске приложения и попытке выполнить расчет площади, мы получим ошибку. Это связано с тем, что при создании кнопки уже вызывается метод и в него передаются пустые параметры, что вызывает ошибку в методе calculate_area_with_base_and_height()

Поэтому нужно передать функцию вот таким способом:


    command=lambda: calculate_area_with_base_and_height(base_ent.get(), height_ent.get())

В таком случае методы get() для полей ввода будут вызываться только при нажатии кнопки. Это связано со спецификой конкретно нашего приложения: если бы все виджеты отрисовывались в основном теле программы, а не в разных методах, то мы могли ничего не передавать в параметры метода, а просто вызывать внутри get(). Тогда command выглядела бы следующим образом (обратим внимание, что скобки уже не нужно указывать):


    command=calculate_area_with_base_and_height

Выглядеть окно будет так: 

Добавление кнопки

Для данного метода расчета площади теперь есть все необходимые графические элементы. 

Информационное окно

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


    def show_result(area): 
    area = round(area, 2)   
    messagebox.showinfo('Результат', f'Площадь треугольника равна {area}')

Для более красивого вывода округлим значение площади до двух знаков после запятой. В метод showinfo() передаем название окна и сам текст сообщения.

Есть и другие разновидности этого метода: showwarning() и showerror(). Их полезно применять, если нужно показать пользователю предупреждающее сообщение или сообщение об ошибке.

Осталось написать функцию, считающую площадь по заданной формуле и передающую результат в метод show_result():


    def calculate_area_with_base_and_height(base, height):
    base = float(base)
    height = float(height)
    area = float(base * height / 2)
    show_result(area)

В результате программа будет выглядеть следующим образом:

Вывод результата в всплывающей подсказке

Теперь нужно реализовать подобный функционал и для других способов расчета площади. Для удобства можно разбить программу на несколько файлов.

Начнем с самого метода method_with_sides_and_angle(). Теперь необходимо реализовать уже три поля для ввода величин двух сторон и угла между ними. Аналогично предыдущему способу используем для этого Label и Entry:


    a_lbl = Label(
    frame,
    text="Сторона а"
)
a_lbl.grid(row=3, column=1, pady=10)
a_ent = Entry(
    frame
)
a_ent.grid(row=3, column=2)
b_lbl = Label(
    frame,
    text="Сторона b"
)
b_lbl.grid(row=4, column=1, pady=10)
b_ent = Entry(
    frame
)
b_ent.grid(row=4, column=2)
alpha_lbl = Label(
    frame,
    text="Угол α"
)
alpha_lbl.grid(row=5, column=1, pady=10)
alpha_ent = Entry(
    frame
)
alpha_ent.grid(row=5, column=2)

Добавив этот фрагмент, мы получим вот такое окно:

Три поля ввода

Radiobutton

Однако пользователь может ввести значение угла в разных единицах измерения, например, в градусах или радианах. Так что стоит добавить возможность выбрать конкретную величину. Для этого мы воспользуемся виджетом Radiobutton.


    var = StringVar()
var.set("degrees")
degrees_rdb = Radiobutton(frame, text='град.', variable=var, value="degrees")
radians_rdb = Radiobutton(frame, text='рад.', variable=var, value="radians")
degrees_rdb.grid(row=5, column=3)
radians_rdb.grid(row=5, column=4)

Здесь мы создаем два объекта переключателя, с помощью которых пользователь сможет выбрать конкретную единицу измерения. Важное уточнение: для каждой кнопки значение value должно быть уникальным.

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

Выбор одной из двух единиц измерения

Также такой кнопке можно добавить уже известный параметр command, но в рамках нашего приложения в этом нет необходимости.

Осталось добавить кнопку и еще один вариант готов:


    calc_btn = Button(
    frame,
    text='Рассчитать площадь',
    command=lambda: calculate_area_with_sides_and_angle(a_ent.get(), b_ent.get(), alpha_ent.get(), var.get())
)
calc_btn.grid(row=6, column=2)

Здесь мы передаем в calculate_area_with_sides_and_angle() помимо параметров треугольника значение var, которое отвечает за единицу измерения углов, что пригодится нам при подсчете площади.

Добавление кнопки

Но сейчас может возникнуть проблема: при переключении на другой метод виджеты не будут корректно отображаться, так как мы не удаляем уже существующие, а условно «рисуем» на них (также это может произойти, если указать для двух разных виджетов одни и те же координаты).

Чтобы решить эту проблему, нужно удалять все лишние виджеты в начале каждого метода. Не лишними виджетами будут надпись «Выберите способ расчета площади» и выпадающий список. Поэтому после их создания в основном теле программы добавим их в специальный список:


    global_widgets = frame.winfo_children() 

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


    def clear_extra_widgets():
    for widget in frame.winfo_children():
        if widget not in global_widgets:
            widget.destroy()

Добавим метод для подсчета площади и еще один вариант готов:


    def calculate_area_with_sides_and_angle(a, b, alpha, angle_unit):
    a = float(a)
    b = float(b)
    alpha = float(alpha)
    if angle_unit == "degrees":
        alpha = convert_degrees_to_radians(alpha)
    area = (a * b * math.sin(alpha)) / 2
    show_result(area)

Функция sin из встроенного модуля math принимает значение угла в радианах, так что добавим метод для конвертирования:


    def convert_degrees_to_radians(alpha):
    return math.pi / 180 * alpha

Все, теперь подсчет площади треугольника через две стороны и угол между ними будет работать правильно:

Вывод работает правильно

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

Еще больше виджетов

Виджетов в Tkinter множество, но, к сожалению, не все они были нужны для реализации приложения. Поэтому сейчас некоторые из них мы рассмотрим отдельно.

Text

Пользоваться виджетом Entry удобно, если пользователь должен ввести какую-то короткую информацию. Однако, если нужно написать большой текст, то для этого лучше подойдет виджет Text.


    box_txt = Text()
box_txt.pack() 

И получим вот такой результат:

Многострочное текстовое поле

Внутри этого окна уже можно писать любой текст. А после получить введенный текст с помощью метода get() и обработать его.

ScrolledText

Для добавления виджета ScrolledText (текстовой области) нужно воспользоваться одноименным классом:


    plants_stx = scrolledtext.ScrolledText(frame, width=40, height=10)
plants_stx.grid(column=1, row=1)

Чтобы виджет не заполнил все окно, нужно задать ему параметры для ширины и высоты. После запуска программы в текстовое поле сразу можно написать текст:

Ввод текста в многострочное поле

Если нам нужно, чтобы после запуска в поле уже был текст, то нужно использовать метод insert():


    plants_stx.insert(INSERT, 'Важная информация про подсолнух') 
Метод insert для текста по-умолчанию

Для очистки содержимого поля, нужно вызвать метод delete() с указанием соответствующих координат:


    plants_stx.delete(0.0, END) 

Диалоговые окна с выбором варианта

У messagebox, помимо showinfo(), который использовался выше, есть и другие интересных методы. Они все в качестве параметров получают заголовок и текст сообщения, но отличаются внешним видом и вариантами ответа, предоставленных пользователю. Рассмотрим их:

  • askquestion() — «да» или «нет»,
  • askyesno() — аналогично askquestion(),
  • askyesnocancel() — «да», «нет» или «отмена»,
  • askokcancel() — «ок» или «отмена»,
  • askretrycancel() — «повтор» или «отмена».

Используя переменную результата, можно узнать выбор пользователя. Эти функции возвращают значение True или False. Однако askyesnocancel() может вернуть третье значение None.

Правила наименования

Виджету можно дать любое имя, не зарегистрированное самим Python. Хорошей практикой считается включать в название переменной имя класса виджета. Так код становится более читабельным и понятным другому человеку. Однако так как название некоторых классов может быть достаточно длинным, принято сокращать их до коротких префиксов или постфиксов: Lable – lbl, Button – btn, Text – txt и так далее.

Заключение

На примере написания несложного калькулятора для подсчета площади треугольника мы познакомились с многофункциональным модулем Tkinter.

С его помощью можно создавать разноплановые приложения с графическим интерфейсом, такие как калькуляторы и текстовые редакторы. Возможности Tkinter позволяют реализовать практически любую идею.