Как создать графическое приложение?
Кто такой GUI и зачем он нужен?
В прошлой главе (фух, это было сложно!) мы с тобой создали текстовый квест, и, в целом, ты теперь умеешь создавать достаточно большие программы в текстовым интерфейсом. Но вот не задача: большинство программ, с которыми мы работаем на ПК - текстовые редакторы, браузеры, проигрыватели фильмов - выглядят совсем по-другому: у них есть кнопки, поля ввода, графика, иными словами - графический интерфейс пользователя (GUI).
Графическим интерфейсом пользователя называют совокупность графических элементов (кнопки, списки, меню, текстовые поля и т.д.), отображаемых на дисплее, которые обеспечивают обработку действий пользователя программой.
Можно ли создавать такие приложения на питоне? Конечно, да! И более того, для этого есть множество разных библиотек, например, PyQt и Kivy (последняя, кстати, позволяет в том числе создавать мобильные приложения), однако устроены они довольно сложно. Поскольку мы с тобой только начинаем, мы возьмем самый простой вариант - библиотеку TkInter
которая, как и random
, time
, winsound
уже встроена в дистрибутив питона.
Я должен заранее признаться, что TkInter - местами достаточно топорный и запутанный инструмент, но зато - максимально простой для освоения. В этой главе я не смогу объяснить все досконально (обычно эту тему проходят чуть позже), зато смогу показать, как уже сейчас ты сможешь добавлять графический интерфейс в свои программы. А постепенно, в дальнейшем, ты будешь понимать что же такое мы написали с технической точки зрения.
В качестве практического проекта в этот раз я предлагаю тебе создать инженерный калькулятор, который ты сможешь использовать для математических расчетов в школе. Но перед этим нам предстоить освоить создание окон, кнопок, текстовых полей и полей для ввода. Поехали! 🚀
Создаем окно приложения
Для начала работы с TkInter, необходимо импортировать модуль:
import tkinter
Однако в этот раз, чтобы не писать постоянно tkinter.
перед каждой командой, давайте импортируем из модуля сразу все, что в нем есть. Делается это следующим образом:
from tkinter import *
Теперь все команды из модуля tkinter
можно писать без префикса tkinter.
, как будто это часть самого языка.
Теперь давай создадим окно и сохраним его в переменную, чтобы потом его можно было менять:
window = Tk()
Только, кажется, есть проблема! Если попробовать запустить наше приложение, оно сразу же закрывается. Для этого нужно попросить питон дождаться, пока пользователь не нажмет на крестик. Для этого в tkinter
встроена функция mainloop()
- она ждет, пока мы не закроем окно, а еще обеспечивает работу кнопок и обработку любых действий пользователя с окном. В общем, важная вещь!
from tkinter import *
window = Tk()
mainloop()
Вот теперь-то все запустилось!
При желании окошку можно задать название:
from tkinter import *
window = Tk()
window.title("Графическое приложение")
mainloop()
Кстати, если у нас есть команда создания окна, мы же наверняка можем запустить ее несколько раз...
from tkinter import *
window1 = Tk()
window2 = Tk()
window3 = Tk()
window4 = Tk()
mainloop()
Добавляем кнопку
Итак, мы научились создавать окна. Только как-то в этих окнах пока пустовато. TkInter позволяет добавлять на окно различные элементы пользовательского интерфейса - виджеты. Давайте что-нибудь добавим, например, кнопку!
from tkinter import *
# создаем окно
window = Tk()
window.title("Графическое приложение")
# создаем кнопку внутри окна window с текстом "Нажми меня!"
Button(window, text="Нажми меня!")
mainloop()
Запускаем... Кажется, кнопки нет. На самом деле, мы просто забыли попросить ее показаться с помощью метода pack()
.
from tkinter import *
# создаем окно
window = Tk()
window.title("Графическое приложение")
# создаем кнопку внутри окна window с текстом "Нажми меня!"
button = Button(window, text="Нажми меня!")
# просим кнопку "нарисоваться"
button.pack()
mainloop()
Вот теперь, конечно, топорно, но кнопка появилась.
В коде выше есть два момента, которые нужно отдельно обсудить. Во-первых, мы с тобой уже видели, что функции могут принимать один или несколько параметров, причем в зависимости от ситуации их может быть разное количество. Например, в print(...)
мы можем передать сколько угодно строк через запятую, а можем не передавать вообще. А вот random.randint(from, to)
хочет от нас ровно два параметра - самое маленькое и самое большое число, которые можно сгенерировать.
Иногда бывает, что необязательных параметров у функции так много, что нужно указать, какие именно мы передаем. В частости, в функцию (сейчас немного лукавлю, но это во благо!) Button(window, text="Нажми меня!")
мы передали окно и текст для кнопки. Первый параметр - это всегда окно, поэтому его имя можно не указывать, а вот чтобы уточнить, что за ним мы передаем именно текст, мы и добавили text=
.
Тогда какие могут быть еще параметры? Их много, например, еще есть ширина и высота.
from tkinter import *
# создаем окно
window = Tk()
window.title("Графическое приложение")
# создаем кнопку внутри окна window с текстом "Нажми меня!"
button = Button(window, text="Нажми меня!", width=30, height=5)
# просим кнопку "нарисоваться"
button.pack()
mainloop()
Хорошо, с этим понятно. А что тогда во-вторых? Возможно ты заметил, что мы как-то странно вызываем функцию button.pack()
? Раньше мы использовали такой синтаксис с точкой для вызова функции из модуля, но ведь button
- это не модуль, а кнопка (что-бы это не было)?
Здесь я должен заметить две вещи: во-первых, в переменных могут лежать не только строки и числа, как было в прошлой главе, но и более сложные объекты, например, кнопки и окна, как мы уже видели в коде выше. А во-вторых, иногда объект из переменной можно не только передать в функцию в качестве параметра, как мы передаем строки в print
и окно в Button
, но и попросить у объекта, чтобы он сделал что-то сделал сам.
Если мы просим объект что-то сделать, то команду для него мы запускаем через точку, такая команда называется методом объекта. Например, у строк есть метод lower()
, который приводит их к нижнему регистру, а у кнопок и других виджетов из TkInter метод pack()
, который просит виджет нарисоваться.
Пример ниже заставляет строку сделать все свои буквы строчными. Подробнее про методы строк мы поговорим в следующей главе, когда будем делать классические текстовые игры.
s = "HaHaHaHa"
print(s)
print(s.lower())
Вывод:
HaHaHaHa
hahahaha
По аналогии работает и отрисовка виджетов. pack
- это метод кнопки, т.е. действие которое мы просим выполнить саму кнопку.
# создаем кнопку внутри окна window с текстом "Нажми меня!"
button = Button(window, text="Нажми меня!", width=30, height=5)
# просим кнопку "нарисоваться"
button.pack()
Кстати, поскольку ничего больше с кнопкой мы делать не хотим, запись можно немного сократить. Вместо того, чтобы сохранять кнопку в переменную, и затем вызывать метод у переменной, мы можем создать кнопку и сразу вызвать метод у нее.
from tkinter import *
# создаем окно
window = Tk()
window.title("Графическое приложение")
# создаем кнопку внутри окна window с текстом "Нажми меня!" и сразу рисуем ее
Button(window, text="Нажми меня!", width=30, height=5).pack()
mainloop()
Обрабатываем нажатие на кнопку
Отлично, теперь у нас есть кнопка, только беда в том, что она ничего не делает. Чтобы заставить нашу программу реагировать на нажатие кнопки, нам понадобится создать функцию (не зря мы научились это делать в прошлой главе!), которую TkInter вызовет в момент нажатия пользователем на кнопку, и указать эту функцию при создании кнопки.
То есть алгоритм действий следующий:
- написать функцию, которую мы хотим вызвать после нажатия кнопки;
- указать название этой функции про создании кнопке в параметре
command
.
from tkinter import *
# описываем функцию my_action
def my_action():
print("Пользователь нажал на кнопку!")
window = Tk()
window.title("Графическое приложение")
# сообщаем имя функции (только имя, без скобочек!) в параметре command
Button(window, text="Нажми меня!", width=30, height=5, command=my_action).pack()
mainloop()
Обрати внимание, что имя функции мы передаем без скобочек, иначе она вызовется только в момент создания кнопки (и больше работать не будет). А мы хотим, чтобы TkInter вызывал ее при каждом нажатии кнопки. После запуска программы в самом окне ничего по-прежнему меняться не будет (а мы ничего и не меняем), а вот в консоли питона будет выводится сообщение при каждом нажатии.
Кстати, ничто не мешает нам добавить вторую кнопку, например, для закрытия окна. Для этого добавим еще одну функцию-обработчик и создадим еще одну кнопку.
from tkinter import *
# описываем функцию my_action
def my_action():
print("Пользователь нажал на кнопку!")
# описываем функцию для закрытия окна
def close_window():
# закрыть окно
quit()
window = Tk()
window.title("Графическое приложение")
# сообщаем имя функции (только имя, без скобочек!) в параметре command
Button(window, text="Нажми меня!", width=30, height=5, command=my_action).pack()
# добавляем вторую кнопку - для закрытия окна, передаем в command нашу функцию close_window
Button(window, text="Закрыть окно", width=30, height=5, command=close_window).pack()
mainloop()
Добавляем текстовое поле
Давайте сделаем приложение, которое при нажатии на кнопку будет показывать пользователю случайную пословицу. Мы уже умеем создавать окно и можем добавить в него кнопку, а вот выводить у нас пока получалось только в консоль питона. Чтобы вывести какой то текст в окно нам потребуется текстовое поле - Label
.
Создается текстовое поле так:
from tkinter import *
window = Tk()
window.title("Графическое приложение")
# создаем текстовое поле в окне window с текстом "Привет, мир"
Label(window, text="Привет, мир!", width=25, height=5).pack()
mainloop()
Для примера, давайте напишем рандомный генератор курса акций Tesla 🚗 . Говорят, они часто делают крутое "пике"!
import random
from tkinter import *
# 1. Описываем функцию, которая будет срабатывать при нажатии на кнопку
def print_price():
# Генерируем случайное число от 100 до 3000
price = random.randint(100, 3000)
# Превращаем число в строку с помощью str и добавляем знак доллара
# Результат отправляем в text_label
text_label.config(text=str(price) + "$")
# 2. Создаем окно
window = Tk()
window.title("Стоимость акций Tesla")
# 3. Добавляем текстовое поле text_label
# и привязываем к нему price_var
text_label = Label(window, width=25, height=5)
text_label.pack()
# 4. Добавляем кнопку
# При нажатии вызывается функция print_price
Button(window, text="Узнать стоимость", width=25, height=5, command=print_price).pack()
mainloop()
Теперь при каждом нажатии наша рандомная стоимость будет меняться.