← Часть 19 Основы ООП – абстракция и полиморфизм
Графический интерфейс для программы на Python можно создать с помощью одной из специальных GUI библиотек:
Tkinter
wxPython
PyQt
PySide
Kivy
PyGTK
У каждой из этих библиотек есть свои преимущества и недостатки. Только одна из них, Tkinter, входит в стандартную поставку Python. Виджеты Tkinter не отличаются сногсшибательной стильностью – это ее единственный очевидный минус. Преимуществ у Tkinter гораздо больше, чем недостатков. Эта библиотека:
Максимально проста в изучении и использовании.
Имеет детальную и доступную документацию.
Помимо базовых элементов интерфейса, содержит два мощных виджета – Text (многострочное текстовое поле с поддержкой форматирования) и Canvas («холст», на котором можно рисовать и отображать любые графические объекты).
Включает в себя модуль ttk , который предоставляет в распоряжение разработчика набор дополнительных виджетов – Combobox , Notebook , Treeview , Progressbar , Scale и другие.
Позволяет сделать интерфейс адаптивным.
Отлично подходит для начинающих – поэтому на ней мы и сосредоточимся.
Создание окна приложения с Tkinter
Для создания простого окна приложения в Tkinter выполните следующие действия:
Импортируйте модуль import tkinter
.
Создайте новый объект Tk
, который представляет собой главное окно приложения.
(Опционально) Задайте заголовок окна с помощью метода title()
объекта Tk
.
(Опционально) Установите размер окна с помощью метода geometry()
объекта Tk. Метод geometry()
принимает строковый параметр в формате "ширинаx высота"
.
Вызовите метод mainloop()
объекта Tk, чтобы запустить основной цикл GUI приложения.
Вот пример кода, который создает простое окно размером 250 на 250 пикселей с заголовком «Мое приложение»:
import tkinter as tk # Создайте новый объект Tk root = tk.Tk() # Задайте заголовок окна root.title("Мое приложение") # Установите размер окна root.geometry("250x250") # Запустите основной цикл root.mainloop()
Позиционирование окна в центре экрана
Чтобы разместить окно Tkinter приложения в центре экрана, необходимо:
Воспользоваться методами winfo_screenwidth() и winfo_screenheight() для получения ширины и высоты экрана соответственно.
Передать в метод geometry() координаты x и y , равные половине ширины и высоты экрана за минусом половины ширины и высоты окна приложения.
Код будет выглядеть так:
import tkinter as tk # Создаем окно root = tk.Tk() # Получаем ширину и высоту экрана screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() # Вычисляем координаты окна приложения window_width = 500 window_height = 300 x = (screen_width // 2) - (window_width // 2) y = (screen_height // 2) - (window_height // 2) root.geometry(f"{window_width}x{window_height}+{x}+{y}") # Запускаем программу root.mainloop()
Теперь окно расположено точно в центре экрана:
Размещение элементов интерфейса в Tkinter
Элементы интерфейса в Tkinter называются виджетами. Существует три основных способа расположения виджетов на поверхности окна: pack() , place() и grid() . Каждый из этих методов имеет свои преимущества и недостатки, и выбор оптимального способа зависит от конкретной ситуации.
pack() – упорядочивает виджеты по горизонтали или вертикали. Он прост в использовании, не требует дополнительных параметров (указания отступов, конкретной позиции). Подходит для создания простых интерфейсов. Недостаток – с помощью pack() проблематично реализовать сложную компоновку, например, сетку или перекрывание виджетов: для этого нужно комбинировать метод с place().
import tkinter as tk root = tk.Tk() label1 = tk.Label(root, text="Это пример использования pack()") label1.pack() button1 = tk.Button(root, text="Нажми!") button1.pack() root.mainloop()
place() – позволяет задать точное положение и размер каждого виджета в окне. Его используют, когда необходимо точно расположить виджеты или создать перекрывающиеся элементы интерфейса. Недостаток – при изменении размеров окна или содержимого виджетов трудно сохранить расположение элементов.
import tkinter as tk root = tk.Tk() label1 = tk.Label(root, text="Это пример использования place()") label1.place(x=5, y=50) button1 = tk.Button(root, text="Нажми кнопку") button1.place(x=50, y=100) root.mainloop()
grid() – упорядочивает виджеты в сетку из рядов и столбцов. Самый гибкий и мощный, позволяет создавать сложные интерфейсы, состоящие из виджетов разных размеров. Сложнее в использовании, чем pack(), поскольку требует больше кода для компоновки виджетов.
import tkinter as tk root = tk.Tk() root.title("Пример grid()") root.geometry("250x250") frame = tk.Frame(root) frame.pack(expand=True) label1 = tk.Label(frame, text="Имя:") label1.grid(row=0, column=0) entry1 = tk.Entry(frame) entry1.grid(row=0, column=1) label2 = tk.Label(frame, text="Email:") label2.grid(row=1, column=0) entry2 = tk.Entry(frame) entry2.grid(row=1, column=1) button1 = tk.Button(frame, text="Отправить") button1.grid(row=2, column=1) root.update_idletasks() width = root.winfo_width() height = root.winfo_height() x = (root.winfo_screenwidth() // 2) - (width // 2) y = (root.winfo_screenheight() // 2) - (height // 2) root.geometry('{}x{}+{}+{}'.format(width, height, x, y)) root.mainloop()
В целом, pack() хорошо подходит для простых интерфейсов, place() – для более сложных, а grid() используют для создания сложных интерфейсов, которым нужна адаптивность, особое позиционирование или растягивание виджетов на несколько строк / столбцов.
Связывание виджетов с функциями
Чтобы при нажатии кнопки выполнялось какое-то действие, нужно связать кнопку с определенной функцией. Чаще всего для этого используются методы command() и bind() , но при необходимости к виджетам Tkinter можно привязывать выполнение анонимных и частично примененных функций. Проиллюстрируем примерами.
Метод command() используется для прямого связывания функции с нажатием кнопки:
import tkinter as tk root = tk.Tk() def say_hello(): print("Привет!") button1 = tk.Button(root, text="Поздоровайся", command=say_hello) button1.pack() root.mainloop()
Метод bind() , по сравнению с command(), отличается большей гибкостью: его можно использовать для связывания функции с любым событием, происходящим в виджете – с нажатием кнопки, движением мыши, нажатием клавиши, изменением размера окна и так далее:
import tkinter as tk root = tk.Tk() def say_hello(event=None): print("Привет от метода bind()") button1 = tk.Button(root, text="Нажми кнопку") button1.bind("<Button-1>", say_hello) button1.pack() root.mainloop()
Назначение кнопке анонимной функции:
import tkinter as tk root = tk.Tk() button1 = tk.Button(root, text="Нажми!", command=lambda: print("Привет от анонимной функции!")) button1.pack() root.mainloop()
Связывание кнопки с частично примененной функцией:
import tkinter as tk from functools import partial root = tk.Tk() def say_hello(name): print(f"Привет, {name}!") button1 = tk.Button(root, text="Нажми эту кнопку", command=partial(say_hello, "пользователь")) button1.pack() root.mainloop()
Практика
Задание 1
Создайте Tkinter интерфейс для программы, которая получает от пользователя текст с помощью виджетов Entry и Button , а затем выводит полученную строку в терминале.
Ожидаемый результат:
Решение:
import tkinter as tk root = tk.Tk() def button_click(): input_text = entry.get() print(f"Полученный текст: {input_text}") entry = tk.Entry(root) button = tk.Button(root, text="Отправить", command=button_click) entry.pack() button.pack() root.mainloop()
Задание 2
Создайте интерфейс для программы, которая изменяет текст виджета Label после нажатия на кнопку.
Ожидаемый результат:
Решение:
import tkinter as tk root = tk.Tk() label = tk.Label(root, text="Измени этот текст") label.pack() def change_text(): label.config(text="Это новый текст") button = tk.Button(root, text="Нажми кнопку!", command=change_text) button.pack() root.mainloop()
Задание 3
Создайте интерфейс для программы, которая получает от пользователя многострочный текст в виджете Text и выводит в виджетах Label количество слов и символов.
Ожидаемый результат:
import tkinter as tk import tkinter.ttk as ttk root = tk.Tk() root.geometry("250x150") root.title("Подсчет слов и символов") def count_words_characters(): sentence = sentence_entry.get("1.0", "end-1c") words = len(sentence.split()) characters = len(sentence) words_label.config(text=f"Количество слов: {words}") characters_label.config(text=f"Количество символов: {characters}") sentence_entry = tk.Text(root, height=3, wrap="word") words_label = ttk.Label(root) characters_label = ttk.Label(root) count_button = ttk.Button(root, text="Подсчитать", command=count_words_characters) sentence_entry.pack() words_label.pack() characters_label.pack() count_button.pack() root.mainloop()
Задание 4
Напишите программу для оценки сервиса по шкале от 0 до 100. Используйте виджет Scale (ttk модуль).
Ожидаемый результат:
Решение:
import tkinter as tk root = tk.Tk() label = tk.Label(root, text=f"Ваша оценка от 0 до 100") label.pack() def update_label(value): label.config(text=f"Ваша оценка: {value}") scale = tk.Scale(root, from_=0, to=100, orient="horizontal", command=update_label) scale.pack() root.mainloop()
Задание 5
Создайте индикатор выполнения задачи с помощью виджета Progressbar из модуля ttk. Индикатор должен обнуляться спустя 5 секунд.
Ожидаемый результат:
Решение:
import tkinter as tk from tkinter import ttk root = tk.Tk() progressbar = ttk.Progressbar(root, orient="horizontal", length=200, mode="determinate") progressbar.start() def stop_progressbar(): if root.winfo_exists() and progressbar.winfo_exists(): progressbar.stop() progressbar["value"] = 0 root.quit() root.destroy() if root.winfo_exists() and progressbar.winfo_exists(): root.after(5000, stop_progressbar) progressbar.pack() def exit_app(): stop_progressbar() root.protocol("WM_DELETE_WINDOW", exit_app) root.mainloop()
Задание 6
Создайте Tkinter интерфейс для программы заказа фруктов. Для вывода списка фруктов используйте виджет Combobox из модуля ttk.
Ожидаемый результат:
Решение:
import tkinter as tk import tkinter.ttk as ttk root = tk.Tk() root.title("Заказ") def select_item(): selected_item = combobox.get() selection_label.config(text=f"Выбрано: {selected_item}, 1 кг") items = ["Яблоки", "Апельсины", "Виноград", "Персики", "Клубника"] combobox = ttk.Combobox(root, values=items) selection_label = ttk.Label(root) select_button = ttk.Button(root, text="Заказать", command=select_item) combobox.pack() selection_label.pack() select_button.pack() root.mainloop()
Задание 7
Напишите GUI программу, которая позволяет пользователю выбрать цвет из палитры, и отображает его HEX-значение.
Ожидаемый результат:
Решение:
import tkinter as tk import tkinter.colorchooser as cc root = tk.Tk() root.geometry("250x200") root.title("Выбор цвета") def choose_color(): color = cc.askcolor()[1] color_label.config(text=f"Вы выбрали цвет {color}") second_label.config(bg=color) color_button = tk.Button(root, text="Выбрать цвет", command=choose_color) color_label = tk.Label(root, text="Нажмите кнопку, чтобы выбрать цвет") second_label = tk.Label(root, text="tttnttt") color_button.pack(pady=10) color_label.pack() second_label.pack(pady=10) root.mainloop()
Задание 8
Напишите программу, которая использует виджеты Spinbox для получения двух чисел a и b (0 <= a <= 100, 0 <= b <= 100), а затем выводит сумму всех целых чисел в диапазоне от a до b включительно .
Ожидаемый результат:
Решение:
import tkinter as tk import tkinter.ttk as ttk class SumApp: def __init__(self, root): self.root = root root.geometry("250x200") self.root.title("Сумма чисел") self.start_spinbox = ttk.Spinbox(root, from_=0, to=100, increment=1) self.end_spinbox = ttk.Spinbox(root, from_=0, to=100, increment=1) self.sum_label = tk.Label(root, text="") self.calc_button = tk.Button(root, text="Вычислить сумму", command=self.calculate_sum) self.start_spinbox.pack(pady=10) self.end_spinbox.pack(pady=10) self.sum_label.pack(pady=10) self.calc_button.pack(pady=10) def calculate_sum(self): start = int(self.start_spinbox.get()) end = int(self.end_spinbox.get()) sum_ = sum(range(start, end+1)) self.sum_label.configure(text=f"Сумма чисел в диапазоне от {start} до {end}: {sum_}") root = tk.Tk() app = SumApp(root) root.mainloop()
Задание 9
Создайте GUI интерфейс для программы-каталога фильмов, книг и мультфильмов. Используйте виджеты Notebook и Treeview для вывода объектов разного типа на отдельных вкладках. Реализуйте всплывающее окно для вывода полной информации об издании.
Ожидаемый результат:
Решение:
import tkinter as tk from tkinter import ttk def retrieve_details(event): item = event.widget.focus() values = event.widget.item(item)['values'] details = f"Информация об издании:nn" for i in range(len(values)): details += f"{event.widget['columns'][i].capitalize()}: {values[i]}n" popup = tk.Toplevel(root) popup.title(event.widget.item(item)['text']) popup.geometry("250x150") popup_label = ttk.Label(popup, text=details) popup_label.pack(pady=10) close_button = ttk.Button(popup, text="Закрыть", command=popup.destroy) close_button.pack() root = tk.Tk() root.title("Каталог") root.geometry("600x300") notebook = ttk.Notebook(root) movies_tab = ttk.Frame(notebook) books_tab = ttk.Frame(notebook) animation_tab = ttk.Frame(notebook) notebook.add(movies_tab, text="Фильмы") notebook.add(books_tab, text="Книги") notebook.add(animation_tab, text="Мультфильмы") movies_desc = ttk.Label(movies_tab, text="Список фильмов") books_desc = ttk.Label(books_tab, text="Список книг") animation_desc = ttk.Label(animation_tab, text="Список мультфильмов") movies_list = ttk.Treeview(movies_tab) books_list = ttk.Treeview(books_tab) animation_list = ttk.Treeview(animation_tab) movies_list['columns'] = ('Жанр', 'Режиссер', 'Год') books_list['columns'] = ('Автор', 'Издательство', 'Год') animation_list['columns'] = ('Жанр', 'Студия', 'Рейтинг') for tree in [movies_list, books_list, animation_list]: for i, col in enumerate(tree['columns']): tree.column(col, width=100, anchor='center') tree.heading(col, text=col.capitalize()) tree.bind("<Double-1>", retrieve_details) movies_list.insert('', 'end', text='М3ган', values=('Фантастика', 'Джерард Джонстоун', '2022')) books_list.insert('', 'end', text='Перекрестки', values=('Джонатан Франзен', 'Corpus', '2022')) animation_list.insert('', 'end', text='Пиноккио Гильермо Дель Торо', values=('фэнтези', 'Disney+', '8.2')) movies_desc.pack() movies_list.pack() books_desc.pack() books_list.pack() animation_desc.pack() animation_list.pack() notebook.pack(fill='both', expand=True) root.mainloop()
Задание 10
Напишите GUI калькулятор, который:
Выполняет основные арифметические операции – сложение, вычитание, деление и умножение.
Поддерживает операции с отрицательными числами, извлечение квадратного корня, деление с остатком и целочисленное деление.
Кроме того, программа должна поддерживать очистку ввода.
Ожидаемый результат:
Решение:
import tkinter as tk from tkinter import ttk from math import sqrt class Calculator: def __init__(self, master): self.master = master self.master.title("Калькулятор") self.master.geometry("380x140") self.number_entry = ttk.Entry(self.master, width=20) self.number_entry.grid(row=0, column=0, columnspan=5, padx=5, pady=5) self.button_1 = ttk.Button(self.master, text="1", command=lambda: self.button_click(1)) self.button_2 = ttk.Button(self.master, text="2", command=lambda: self.button_click(2)) self.button_3 = ttk.Button(self.master, text="3", command=lambda: self.button_click(3)) self.button_4 = ttk.Button(self.master, text="4", command=lambda: self.button_click(4)) self.button_5 = ttk.Button(self.master, text="5", command=lambda: self.button_click(5)) self.button_6 = ttk.Button(self.master, text="6", command=lambda: self.button_click(6)) self.button_7 = ttk.Button(self.master, text="7", command=lambda: self.button_click(7)) self.button_8 = ttk.Button(self.master, text="8", command=lambda: self.button_click(8)) self.button_9 = ttk.Button(self.master, text="9", command=lambda: self.button_click(9)) self.button_0 = ttk.Button(self.master, text="0", command=lambda: self.button_click(0)) self.button_clear = ttk.Button(self.master, text="C", command=self.button_clear) self.button_add = ttk.Button(self.master, text="+", command=self.button_add) self.button_equal = ttk.Button(self.master, text="=", command=self.button_equal) self.button_subtract = ttk.Button(self.master, text="-", command=self.button_subtract) self.button_multiply = ttk.Button(self.master, text="*", command=self.button_multiply) self.button_divide = ttk.Button(self.master, text="/", command=self.button_divide) self.button_floor_div = ttk.Button(self.master, text="//", command=self.button_floor_div) self.button_modulus = ttk.Button(self.master, text="%", command=self.button_modulus) self.button_sqrt = ttk.Button(self.master, text="√", command=self.button_sqrt) self.button_neg = ttk.Button(self.master, text="+/-", command=self.button_neg) self.button_1.grid(row=1, column=0) self.button_2.grid(row=1, column=1) self.button_3.grid(row=1, column=2) self.button_add.grid(row=1, column=3) self.button_floor_div.grid(row=1, column=4) self.button_4.grid(row=2, column=0) self.button_5.grid(row=2, column=1) self.button_6.grid(row=2, column=2) self.button_subtract.grid(row=2, column=3) self.button_modulus.grid(row=2, column=4) self.button_7.grid(row=3, column=0) self.button_8.grid(row=3, column=1) self.button_9.grid(row=3, column=2) self.button_multiply.grid(row=3, column=3) self.button_sqrt.grid(row=3, column=4) self.button_clear.grid(row=4, column=0) self.button_0.grid(row=4, column=1) self.button_equal.grid(row=4, column=2) self.button_divide.grid(row=4, column=3) self.button_neg.grid(row=4, column=4) self.f_num = 0 self.math = "" def button_click(self, number): current = self.number_entry.get() self.number_entry.delete(0, tk.END) self.number_entry.insert(0, str(current) + str(number)) def button_clear(self): self.number_entry.delete(0, tk.END) def button_add(self): first_number = self.number_entry.get() self.math = "addition" self.f_num = int(first_number) self.number_entry.delete(0, tk.END) def button_equal(self): second_number = self.number_entry.get() self.number_entry.delete(0, tk.END) if self.math == "addition": self.number_entry.insert(0, self.f_num + int(second_number)) if self.math == "multiplication": self.number_entry.insert(0, self.f_num * int(second_number)) if self.math == "division": self.number_entry.insert(0, self.f_num / int(second_number)) if self.math == "floor_div": self.number_entry.insert(0, self.f_num // int(second_number)) if self.math == "modulus": self.number_entry.insert(0, self.f_num % int(second_number)) def button_subtract(self): first_number = self.number_entry.get() self.math = "subtraction" self.f_num = int(first_number) self.number_entry.delete(0, tk.END) def button_multiply(self): first_number = self.number_entry.get() self.math = "multiplication" self.f_num = int(first_number) self.number_entry.delete(0, tk.END) def button_divide(self): first_number = self.number_entry.get() self.math = "division" self.f_num = int(first_number) self.number_entry.delete(0, tk.END) def button_floor_div(self): first_number = self.number_entry.get() self.math = "floor_div" self.f_num = int(first_number) self.number_entry.delete(0, tk.END) def button_modulus(self): first_number = self.number_entry.get() self.math = "modulus" self.f_num = int(first_number) self.number_entry.delete(0, tk.END) def button_sqrt(self): number = float(self.number_entry.get()) result = sqrt(number) if result.is_integer(): self.number_entry.delete(0, tk.END) self.number_entry.insert(0, int(result)) else: self.number_entry.delete(0, tk.END) self.number_entry.insert(0, result) def button_neg(self): current = self.number_entry.get() if current.startswith("-"): current = current[1:] else: current = "-" + current self.number_entry.delete(0, tk.END) self.number_entry.insert(0, current) if __name__ == '__main__': root = tk.Tk() calc = Calculator(root) root.mainloop()
Подведем итоги
В этой статье мы рассмотрели основы создания GUI для Python-программ и научились использовать основные виджеты Tkinter. В следующей главе будем изучать основы разработки игр с Pygame.
***
Содержание самоучителя
Особенности, сферы применения, установка, онлайн IDE
Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
Типы данных: преобразование и базовые операции
Методы работы со строками
Методы работы со списками и списковыми включениями
Методы работы со словарями и генераторами словарей
Методы работы с кортежами
Методы работы со множествами
Особенности цикла for
Условный цикл while
Функции с позиционными и именованными аргументами
Анонимные функции
Рекурсивные функции
Функции высшего порядка, замыкания и декораторы
Методы работы с файлами и файловой системой
Регулярные выражения
Основы скрапинга и парсинга
Основы ООП: инкапсуляция и наследование
Основы ООП: абстракция и полиморфизм
Графический интерфейс на Tkinter