Программирование графического интерфейса пользователя с помощью Tkinter в Python

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

Содержание

Введение в Tkinter

Что такое Tkinter и зачем он нужен?

Tkinter — это стандартная библиотека для создания графического интерфейса пользователя (GUI) в языке программирования Python. Tkinter предоставляет доступ к Tk GUI toolkit, который является традиционным инструментарием для создания графических интерфейсов в Tcl/Tk. Используя Tkinter, разработчики могут быстро создавать интуитивно понятные и визуально привлекательные десктопные приложения.

Зачем нужен Tkinter?

  1. Простота использования: Tkinter достаточно прост для освоения начинающими программистами, что делает его идеальным выбором для тех, кто только начинает изучать создание GUI.
  2. Переносимость: Приложения, написанные с использованием Tkinter, могут работать на различных операционных системах, включая Windows, macOS и Linux, без изменения исходного кода.
  3. Широкая функциональность: Tkinter поддерживает множество стандартных виджетов, таких как кнопки, текстовые поля, метки и другие, которые используются для создания полноценных пользовательских интерфейсов.
  4. Доступность: Tkinter входит в стандартную библиотеку Python, что означает, что для большинства пользователей нет необходимости устанавливать дополнительные пакеты.

Как установить Tkinter

Tkinter обычно уже входит в стандартную установку Python, поэтому дополнительных действий для его установки может не потребоваться. Однако, если по каким-то причинам его нет в вашей системе, вы можете установить его самостоятельно.

Установка Tkinter

  • Для пользователей Windows и macOS: Tkinter идёт предустановленным с официальными дистрибутивами Python, которые можно скачать с сайта Python.org.
  • Для пользователей Linux: В зависимости от дистрибутива, Tkinter можно установить с помощью менеджера пакетов системы:
    • Для Debian и Ubuntu:
      sudo apt-get install python3-tk
    • Для Fedora:
      sudo dnf install python3-tkinter
    • Для CentOS:
      sudo yum install python3-tkinter

Проверка установки Tkinter

Чтобы проверить, установлен ли Tkinter, попробуйте выполнить следующий Python-скрипт:

import tkinter
tkinter._test()

Если вы видите небольшое окно с кнопкой ‘Quit’, то Tkinter установлен правильно.

Обзор основных компонентов Tkinter

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

Tk — основное окно приложения

Класс Tk представляет собой основное окно приложения. Создание экземпляра этого класса является первым шагом в создании любого GUI-приложения на Tkinter.

import tkinter as tk

root = tk.Tk()
root.mainloop()

Label — метка для отображения текста

Label используется для отображения текста или изображений. Текст может быть статичным или динамичным.

label = tk.Label(root, text="Hello, Tkinter

Создание основного окна в Tkinter

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

Импорт Tkinter

Перед тем как использовать Tkinter, его необходимо импортировать. В Python это делается с помощью ключевого слова import. Tkinter можно импортировать разными способами, но один из наиболее частых — импортировать его с псевдонимом tk. Это делает код короче и проще для чтения.

import tkinter as tk

Теперь, используя tk, вы можете обращаться ко всем доступным классам и функциям в Tkinter.

Создание главного окна приложения

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

root = tk.Tk()

Здесь переменная root теперь представляет основное окно приложения. С этого момента вы можете добавлять в root различные виджеты и управлять его свойствами.

Методы управления основным окном

mainloop

Метод mainloop является одним из важнейших в Tkinter, поскольку он запускает цикл обработки событий. После того как вы создали интерфейс и настроили все необходимые параметры окна, вызов mainloop запустит приложение, и оно будет ждать действий пользователя (например, нажатия кнопок, ввода текста и так далее).

root.mainloop()

Без вызова mainloop приложение откроется и тут же закроется, так как Python завершит выполнение программы.

geometry

Метод geometry используется для задания начальных размеров окна и его позиции на экране. Он принимает строку в формате "ширинаxвысота+Xoffset+Yoffset".

  • "ширинаxвысота" задаёт размеры окна в пикселях.
  • Xoffset и Yoffset задают смещение окна относительно верхнего левого угла экрана (также в пикселях).

Пример:

root.geometry("400x300+100+50")

Это означает, что окно будет иметь ширину 400 пикселей и высоту 300 пикселей, и будет расположено на расстоянии 100 пикселей от левого края экрана и 50 пикселей от верхнего края экрана.

title

Метод title задаёт заголовок окна. Это та строка, которая отображается в заголовочной части окна, обычно вверху.

root.title("Мое первое приложение на Tkinter")

Теперь в верхней части окна вместо стандартного заголовка будет написано «Мое первое приложение на Tkinter».

resizable

Метод resizable определяет, может ли пользователь изменять размер окна, перетаскивая его границы. Метод принимает два параметра: флаг для ширины и флаг для высоты. Каждый из них может быть True (разрешить изменение размера) или False (запретить изменение размера).

root.resizable(True, False)

В этом примере пользователь сможет изменять размер окна по горизонтали (ширина), но не сможет изменять его по вертикали (высота).

Полный пример

Давайте соберём все вместе, чтобы увидеть, как это работает в полноценном минимальном приложении:

import tkinter as tk

# Создаем основное окно приложения
root = tk.Tk()

# Установим размеры и позицию окна
root.geometry("400x300+100+50")

# Задаем заголовок окна
root.title("Мое первое приложение на Tkinter")

# Настраиваем возможности изменения размеров окна
root.resizable(True, False)

# Запускаем главный цикл обработки событий
root.mainloop()

Теперь у вас есть основа, на которой можно строить более сложные и интересные приложения на Tkinter!

Пустое приложение на tkinter

Виджеты в Tkinter

Виджеты — это элементы графического интерфейса пользователя (GUI), которые представляют собой контролы или компоненты, используемые для взаимодействия с пользователем. В Tkinter каждый виджет — это объект класса, который предоставляет определённые стандартные методы для управления его поведением и внешним видом.

Что такое виджеты?

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

Основные виджеты и их использование

Label — для отображения текста

Label — это виджет, который используется для отображения текста или изображений. Текст в метке не может быть изменён пользователем, что делает Label идеальным для меток, подписей и инструкций в интерфейсе.

Пример использования Label:

import tkinter as tk

root = tk.Tk()
root.title("Label Example")

# Создаём метку с текстом
label = tk.Label(root, text="Привет, Tkinter!", font=("Arial", 14))
label.pack()

root.mainloop()
  • text — свойство, которое определяет текст, отображаемый на метке.
  • font — свойство, которое определяет шрифт текста. В примере используется шрифт Arial размером 14.

label ktinter

Button — для создания кнопок

Button — виджет, который отображает кнопку, на которую пользователь может нажимать. Кнопки используются для выполнения действий при нажатии, например, для запуска функций или обработки данных.

Пример использования Button:

def button_clicked():
    print("Кнопка была нажата!")

root = tk.Tk()
root.title("Button Example")

# Создаём кнопку с текстом и функцией, которая будет вызвана при нажатии
button = tk.Button(root, text="Нажми меня", command=button_clicked)
button.pack()

root.mainloop()
  • text — свойство, которое определяет текст на кнопке.
  • command — свойство, которое принимает имя функции (без скобок), которая будет вызвана при нажатии на кнопку.

tkinter button

Entry — для ввода текста пользователем

Entry — это виджет, который позволяет пользователю ввести одну строку текста. Этот виджет обычно используется для получения коротких пользовательских вводов, например, имени, пароля или другой информации.

Пример использования Entry:

root = tk.Tk()
root.title("Entry Example")

# Создаём текстовое поле для ввода
entry = tk.Entry(root, font=("Arial", 14))
entry.pack()

root.mainloop()
  • font — свойство, которое определяет шрифт текста в поле ввода.

tkinter entry

Text — для многострочного ввода текста

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

Пример использования Text:

root = tk.Tk()
root.title("Text Example")

# Создаём многострочное текстовое поле
text = tk.Text(root, height=10, width=40, font=("Arial", 14))
text.pack()

root.mainloop()
  • height и width — свойства, которые определяют размер текстового поля в строках и символах соответственно.
  • font — свойство, которое определяет шрифт текста.

tkinter text

Frame — для организации других виджетов

Frame — это контейнер-виджет, который используется для группировки и организации других виджетов в приложении. Это может быть полезно для логического разделения интерфейса на секции.

Пример использования Frame:

root = tk.Tk()
root.title("Frame Example")

# Создаём фрейм
frame = tk.Frame(root, borderwidth=2, relief="sunken")
frame.pack(padx=10, pady=10)

# Добавляем виджеты в фрейм
label = tk.Label(frame, text="Внутри фрейма")
label.pack(padx=10, pady=10)

button = tk.Button(frame, text="Кнопка")
button.pack(pady=5)

root.mainloop()
  • borderwidth и relief — свойства, которые определяют стиль границы фрейма.
  • padx и pady — параметры метода pack, которые добавляют отступы вокруг фрейма и его содержимого.

tkinter frame

Canvas — для рисования графических фигур

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

Пример использования Canvas:

root = tk.Tk()
root.title("Canvas Example")

# Создаём холст для рисования
canvas = tk.Canvas(root, width=400, height=300)
canvas.pack()

# Рисуем разные фигуры
canvas.create_line(50, 50, 200, 50, fill="blue", width=5)
canvas.create_rectangle(100, 100, 300, 200, outline="red", fill="yellow", width=2)
canvas.create_oval(100, 100, 300, 200, fill="green")

root.mainloop()
  • width и height — свойства, которые определяют размеры холста.
  • create_linecreate_rectanglecreate_oval — методы Canvas, которые рисуют линию, прямоугольник и овал соответственно.

tkinter canvas

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

Практическое задание на закрепление материала по Tkinter

Задание: Создание простой формы регистрации

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

  1. Поля ввода:
    • Имя пользователя (поле ввода Entry)
    • Пароль (поле ввода Entry с маскировкой ввода)
    • Подтверждение пароля (поле ввода Entry с маскировкой ввода)
  2. Кнопку «Зарегистрироваться» (Button), при нажатии на которую проверяется:
    • Заполнены ли все поля.
    • Совпадают ли пароль и подтверждение пароля.
  3. Метки (Label) для каждого поля и возможных сообщений об ошибке.
  4. При успешной регистрации (все поля заполнены, пароли совпадают) выводится сообщение об успешной регистрации.

Общие требования

  • Используйте Frame для организации секций формы.
  • Интерфейс должен быть понятным и удобным для пользователя.

tkinter form

Решение
import tkinter as tk
from tkinter import messagebox

def register():
    # Получаем данные из полей ввода
    username = username_entry.get()
    password = password_entry.get()
    confirm_password = confirm_password_entry.get()
    
    # Проверяем, заполнены ли все поля
    if not username or not password or not confirm_password:
        messagebox.showerror("Ошибка", "Все поля должны быть заполнены")
        return
    
    # Проверяем, совпадают ли пароли
    if password != confirm_password:
        messagebox.showerror("Ошибка", "Пароли не совпадают")
        return
    
    # Если все хорошо, показываем сообщение об успехе
    messagebox.showinfo("Успех", "Регистрация успешно завершена!")
    # Очищаем поля
    username_entry.delete(0, tk.END)
    password_entry.delete(0, tk.END)
    confirm_password_entry.delete(0, tk.END)

# Создаем основное окно
root = tk.Tk()
root.title("Форма регистрации")

# Создаем фрейм для размещения виджетов
frame = tk.Frame(root)
frame.pack(padx=10, pady=10)

# Метка и поле ввода для имени пользователя
username_label = tk.Label(frame, text="Имя пользователя:")
username_label.grid(row=0, column=0, sticky="e", pady=5)

username_entry = tk.Entry(frame)
username_entry.grid(row=0, column=1, pady=5)

# Метка и поле ввода для пароля
password_label = tk.Label(frame, text="Пароль:")
password_label.grid(row=1, column=0, sticky="e", pady=5)

password_entry = tk.Entry(frame, show="*")
password_entry.grid(row=1, column=1, pady=5)

# Метка и поле ввода для подтверждения пароля
confirm_password_label = tk.Label(frame, text="Подтвердите пароль:")
confirm_password_label.grid(row=2, column=0, sticky="e", pady=5)

confirm_password_entry = tk.Entry(frame, show="*")
confirm_password_entry.grid(row=2, column=1, pady=5)

# Кнопка для регистрации
register_button = tk.Button(frame, text="Зарегистрироваться", command=register)
register_button.grid(row=3, column=0, columnspan=2, pady=10)

# Запускаем главный цикл обработки событий
root.mainloop()

Работа с макетами (Layouts) в Tkinter

Понятие макета в Tkinter

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

Менеджеры геометрии в Tkinter — это специальные методы, которые определяют, как виджеты будут располагаться в окне:

  • Автоматические методы подстраивают размеры и позиции виджетов в зависимости от текущего размера окна и других виджетов.
  • Ручные методы позволяют разработчику задавать точные координаты расположения виджетов.

Рассмотрим основные типы макетов (менеджеры геометрии) в Tkinter: packgrid, и place.

Типы макетов

pack — автоматическое размещение виджетов

Метод pack упаковывает виджеты в родительский контейнер (окно или фрейм) в указанном порядке. Он ориентирован на простое вертикальное или горизонтальное размещение.

Основные параметры метода pack:

  • side — определяет сторону, к которой будет прижиматься виджет. Может принимать значения tk.TOP (по умолчанию), tk.BOTTOMtk.LEFTtk.RIGHT.
  • fill — указывает, как виджет будет заполнять выделенное ему пространство. Значения: tk.NONE (по умолчанию), tk.X (растягивание по горизонтали), tk.Y (растягивание по вертикали), tk.BOTH (растягивание в обе стороны).
  • expand — если True, виджет может занимать дополнительное пространство в контейнере.
  • padx и pady — отступы от краёв контейнера по горизонтали и вертикали соответственно.

Пример использования pack:

import tkinter as tk

root = tk.Tk()
root.title("Pack Layout Example")

# Виджеты будут размещены один под другим
label = tk.Label(root, text="Метка вверху")
label.pack(side=tk.TOP, pady=10)

button = tk.Button(root, text="Кнопка в центре")
button.pack(side=tk.TOP, pady=10)

entry = tk.Entry(root)
entry.pack(side=tk.TOP, pady=10)

root.mainloop()

tkinter pack

grid — размещение виджетов в виде таблицы

Метод grid размещает виджеты в двумерной сетке. Это позволяет создавать сложные макеты, где каждый виджет находится в определённой ячейке таблицы.

Основные параметры метода grid:

  • row и column — номера строки и столбца, в которые будет помещён виджет.
  • rowspan и columnspan — количество строк и столбцов, которые виджет будет занимать.
  • sticky — определяет, к каким сторонам ячейки прижимается виджет. Можно использовать значения 'n''s''e''w''ne''nw''se''sw' и их комбинации.
  • padx и pady — отступы от краёв ячейки по горизонтали и вертикали.
  • ipadx и ipady — внутренние отступы виджета.

Пример использования grid:

import tkinter as tk

root = tk.Tk()
root.title("Grid Layout Example")

# Размещаем виджеты в сетке
label1 = tk.Label(root, text="Имя:")
label1.grid(row=0, column=0, sticky="e", padx=5, pady=5)

entry1 = tk.Entry(root)
entry1.grid(row=0, column=1, padx=5, pady=5)

label2 = tk.Label(root, text="Пароль:")
label2.grid(row=1, column=0, sticky="e", padx=5, pady=5)

entry2 = tk.Entry(root)
entry2.grid(row=1, column=1, padx=5, pady=5)

button = tk.Button(root, text="Войти")
button.grid(row=2, column=0, columnspan=2, pady=10)

root.mainloop()

tkinter grid

place — абсолютное позиционирование виджетов

Метод place размещает виджеты по абсолютным координатам (x, y) внутри контейнера. Это даёт полный контроль над положением виджетов, но делает интерфейс менее гибким при изменении размеров окна.

Основные параметры метода place:

  • x и y — координаты верхнего левого угла виджета в контейнере.
  • width и height — ширина и высота виджета.
  • anchor — определяет, к какой точке виджета будут привязаны координаты (x, y). Значения: 'n''s''e''w''center' и др.

Пример использования place:

import tkinter as tk

root = tk.Tk()
root.title("Place Layout Example")

# Размещаем виджеты по абсолютным координатам
label = tk.Label(root, text="Абсолютное позиционирование")
label.place(x=20, y=30)

button = tk.Button(root, text="Кнопка")
button.place(x=160, y=50, width=100, height=30)

root.mainloop()

tkinter place

Резюме и практические советы

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

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

События и обработка событий в Tkinter

Что такое события?

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

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

Примеры событий

  1. Клики мышью: Это одни из самых частых событий. В Tkinter можно реагировать на разные типы кликов: левый клик (<Button-1>), правый клик (<Button-3>), средний клик (<Button-2>), и так далее.
  2. Нажатия клавиш: События, связанные с клавиатурой, возникают, когда пользователь нажимает или отпускает клавишу. В Tkinter они представлены как <KeyPress><KeyRelease>, или можно привязаться к конкретной клавише, например <KeyPress-a> для реакции на нажатие клавиши «A».
  3. Движение мыши: События движения мыши возникают, когда пользователь перемещает курсор мыши. В Tkinter это событие <Motion>.
  4. Изменение размера окна: Событие <Configure> возникает, когда виджет изменяет свои размеры или позицию, что часто используется для обработки изменений размера окна.

Обработка событий

Для реагирования на события в Tkinter используется механизм привязки обработчиков событий (или «event binding»). Это означает, что вы ассоциируете событие с функцией, которая будет вызвана, когда это событие происходит. Эта функция называется «обработчик события» или «callback».

Привязка обработчиков событий к виджетам

Использование метода bind

Метод bind позволяет привязать событие к определённому виджету. Синтаксис:

widget.bind("<Event>", handler)
  • <Event> — имя события в угловых скобках.
  • handler — функция обработчик, которая вызывается при возникновении события.

Пример: Привязка обработчика к клику левой кнопкой мыши на кнопке.

import tkinter as tk

def on_button_click(event):
    print("Кнопка была нажата")

root = tk.Tk()
button = tk.Button(root, text="Нажми меня")
button.pack()

# Привязываем событие клика левой кнопкой мыши к обработчику
button.bind("<Button-1>", on_button_click)

root.mainloop()

tkinter lmouseclick

Использование команд для кнопок

Для кнопок в Tkinter есть более простой способ привязки события клика — использование параметра command при создании кнопки.

import tkinter as tk

def on_button_click():
    print("Кнопка была нажата")

root = tk.Tk()
button = tk.Button(root, text="Нажми меня", command=on_button_click)
button.pack()

root.mainloop()

Привязка к клавиатурным событиям

Для реагирования на нажатия клавиш используется метод bind с указанием конкретной клавиши.

import tkinter as tk

def on_key_press(event):
    print(f"Нажата клавиша: {event.char}")

root = tk.Tk()
text = tk.Text(root)
text.pack()

# Привязываем событие нажатия любой клавиши к обработчику
text.bind("<KeyPress>", on_key_press)

root.mainloop()

tkinter keypress

Привязка к событиям движения мыши

Пример реакции на движение курсора мыши внутри окна:

import tkinter as tk

def on_mouse_move(event):
    print(f"Координаты мыши: {event.x}, {event.y}")

root = tk.Tk()
root.geometry("400x400")

# Привязываем событие движения мыши к обработчику
root.bind("<Motion>", on_mouse_move)

root.mainloop()

ktinter mousemove

Пример обработки событий перетаскивания мышью

Для реализации функционала перетаскивания объектов (drag and drop) в Tkinter мы можем использовать события мыши для отслеживания начала и конца перетаскивания, а также перемещения объекта. В данном примере создадим простое приложение, где можно будет перетаскивать квадрат по холсту Canvas.

Пример реализации drag and drop в Tkinter

import tkinter as tk

class DragDropCanvas:
    def __init__(self, root):
        self.root = root
        self.canvas = tk.Canvas(root, width=400, height=400)
        self.canvas.pack()
        
        # Создаем прямоугольник
        self.rect = self.canvas.create_rectangle(50, 50, 150, 150, fill="blue", outline="white")
        
        # Статус перетаскивания
        self.dragging = False
        
        # Смещение относительно точки захвата
        self.offset_x = 0
        self.offset_y = 0
        
        # Привязка событий
        self.canvas.tag_bind(self.rect, "<ButtonPress-1>", self.start_drag)
        self.canvas.tag_bind(self.rect, "<ButtonRelease-1>", self.stop_drag)
        self.canvas.tag_bind(self.rect, "<B1-Motion>", self.do_drag)

    def start_drag(self, event):
        """ Начало перетаскивания: зафиксируем, где был сделан клик относительно прямоугольника. """
        self.dragging = True
        # Определяем смещение от начала координат фигуры до точки, где был клик
        self.offset_x = self.canvas.coords(self.rect)[0] - event.x
        self.offset_y = self.canvas.coords(self.rect)[1] - event.y

    def stop_drag(self, event):
        """ Окончание процесса перетаскивания """
        self.dragging = False

    def do_drag(self, event):
        """ Обработка процесса перетаскивания """
        if self.dragging:
            # Переместить прямоугольник в новую позицию
            new_x = event.x + self.offset_x
            new_y = event.y + self.offset_y
            self.canvas.coords(self.rect, new_x, new_y, new_x + 100, new_y + 100)

# Создаем главное окно
root = tk.Tk()
root.title("Drag and Drop Example")

# Создаем экземпляр кастомного холста с drag & drop функционалом
drag_drop_canvas = DragDropCanvas(root)

root.mainloop()

Описание кода

  1. Инициализация приложения и холста:
    • Создается класс DragDropCanvas, который принимает главное окно root как параметр.
    • В этом классе создается холст Canvas размером 400×400 пикселей.
  2. Создание перетаскиваемого объекта:
    • На холсте рисуется прямоугольник (self.rect) синего цвета размером 100×100 пикселей.
  3. Инициализация переменных для перетаскивания:
    • self.dragging — флаг, указывающий, идет ли в данный момент перетаскивание.
    • self.offset_x и self.offset_y — смещения от точки клика до верхнего левого угла перетаскиваемого прямоугольника.
  4. Привязка событий:
    • self.canvas.tag_bind(self.rect, "<ButtonPress-1>", self.start_drag) — начало перетаскивания при нажатии левой кнопки мыши.
    • self.canvas.tag_bind(self.rect, "<ButtonRelease-1>", self.stop_drag) — окончание перетаскивания при отпускании кнопки.
    • self.canvas.tag_bind(self.rect, "<B1-Motion>", self.do_drag) — обработка движения мыши с зажатой левой кнопкой.
  5. Методы для обработки событий:
    • start_drag — запоминает начальное смещение от клика до краев фигуры.
    • stop_drag — сбрасывает флаг перетаскивания.
    • do_drag — перемещает прямоугольник в соответствии с текущей позицией мыши и ранее вычисленным смещением.

Как это работает

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

Этот пример демонстрирует базовую реализацию механизма drag and drop в Tkinter, которую можно расширять и адаптировать под различные задачи.

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

Цель:

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

Требования к функционалу:

  1. Поля ввода:
    • Имя (не должно быть пустым)
    • Фамилия (не должно быть пустым)
    • Электронная почта (должна содержать символ «@»)
    • Пароль (должен быть не менее 6 символов)
  2. Кнопки:
    • Кнопка «Зарегистрироваться» — при нажатии должна проверять данные на предмет соответствия требованиям.
  3. Вывод сообщений:
    • При ошибке ввода для каждого поля нужно выводить индивидуальное сообщение об ошибке.
    • При успешной проверке всех полей выводить сообщение: «Регистрация успешна!»

Макет (Layout):

  • Используйте grid() для размещения виджетов.
  • Разместите поля ввода и метки к ним последовательно, начиная с верхнего левого угла.
  • Под каждым полем ввода расположите метку для возможных сообщений об ошибке.
  • Кнопку «Зарегистрироваться» разместите под всеми полями ввода.

Подсказки:

  • Можно использовать метод get() для получения текста из Entry.
  • Используйте функцию bind для обработки событий, например, <Return> для кнопки регистрации.
Решение
import tkinter as tk
from tkinter import messagebox

def validate_data():
    # Сбрасываем предыдущие сообщения об ошибках
    name_error_label.config(text="")
    surname_error_label.config(text="")
    email_error_label.config(text="")
    password_error_label.config(text="")
    
    # Получаем данные из полей ввода
    name = name_entry.get()
    surname = surname_entry.get()
    email = email_entry.get()
    password = password_entry.get()
    
    # Флаг, показывающий, прошла ли валидация успешно
    is_valid = True
    
    # Проверка имени
    if not name:
        name_error_label.config(text="Имя не может быть пустым")
        is_valid = False
    
    # Проверка фамилии
    if not surname:
        surname_error_label.config(text="Фамилия не может быть пустой")
        is_valid = False
    
    # Проверка электронной почты
    if "@" not in email:
        email_error_label.config(text="Email должен содержать @")
        is_valid = False
    
    # Проверка пароля
    if len(password) < 6:
        password_error_label.config(text="Пароль должен быть не менее 6 символов")
        is_valid = False
    
    # Итоговая проверка
    if is_valid:
        messagebox.showinfo("Регистрация", "Регистрация успешна!")
    else:
        messagebox.showerror("Регистрация", "Ошибка в введённых данных")

# Создаем главное окно
root = tk.Tk()
root.title("Форма регистрации")

# Метки для полей ввода
name_label = tk.Label(root, text="Имя:")
name_label.grid(row=0, column=0, sticky='e')

surname_label = tk.Label(root, text="Фамилия:")
surname_label.grid(row=1, column=0, sticky='e')

email_label = tk.Label(root, text="

Создание меню в Tkinter

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

Для создания меню в Tkinter используется класс Menu. Этот класс позволяет создавать как горизонтальные меню (меню верхнего уровня), так и выпадающие подменю.

Основные шаги создания меню:

  1. Создайте объект Menu, который будет основным контейнером для всех пунктов меню.
  2. Добавьте пункты меню или подменю в этот объект.
  3. Присоедините меню к главному окну приложения с помощью метода menu у объекта главного окна.

Подменю и действия в меню

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

Для создания подменю и добавления действий выполните следующие шаги:

  1. Создайте подменю, инициализируя новый объект Menu с параметром tearoff=0, чтобы избежать отрыва подменю от главного окна.
  2. Добавьте элементы в подменю с помощью методов add_commandadd_separatoradd_checkbutton и add_radiobutton.
  3. Добавьте подменю в основное меню с помощью метода add_cascade.

Пример создания меню

Давайте рассмотрим пример, как создать меню с подменю и добавить несколько действий:

import tkinter as tk
from tkinter import messagebox

# Функция, вызываемая при выборе пункта меню
def about():
    messagebox.showinfo("About", "This is a simple Tkinter menu example.")

# Создание главного окна
root = tk.Tk()
root.title("Tkinter Menu Example")
root.geometry("400x250")

# Создание основного меню
main_menu = tk.Menu(root)
root.config(menu=main_menu)

# Создание подменю "File"
file_menu = tk.Menu(main_menu, tearoff=0)
main_menu.add_cascade(label="File", menu=file_menu)

# Добавление действий в подменю "File"
file_menu.add_command(label="New", command=lambda: print("New File"))
file_menu.add_command(label="Open...", command=lambda: print("Open File"))
file_menu.add_separator()
file_menu.add_command(label="Exit", command=root.quit)

# Создание подменю "Edit"
edit_menu = tk.Menu(main_menu, tearoff=0)
main_menu.add_cascade(label="Edit", menu=edit_menu)

# Добавление действий в подменю "Edit"
edit_menu.add_command(label="Cut", command=lambda: print("Cut"))
edit_menu.add_command(label="Copy", command=lambda: print("Copy"))
edit_menu.add_command(label="Paste", command=lambda: print("Paste"))

# Создание подменю "Help"
help_menu = tk.Menu(main_menu, tearoff=0)
main_menu.add_cascade(label="Help", menu=help_menu)

# Добавление действий в подменю "Help"
help_menu.add_command(label="About", command=about)

# Запуск главного цикла окна
root.mainloop()

tkinter menu

Создание контекстного меню в Tkinter

Контекстное меню — это специальный тип меню в графическом интерфейсе пользователя, которое появляется при выполнении определённого действия, например, при нажатии правой кнопки мыши. В Tkinter создание контекстного меню аналогично созданию обычного меню, но с некоторыми особенностями в его вызове и позиционировании.

Шаги для создания контекстного меню:

  1. Создайте основное окно приложения с помощью Tk().
  2. Инициализируйте объект Menu для контекстного меню без привязки его к главному окну сразу.
  3. Добавьте нужные пункты в контекстное меню, используя методы add_commandadd_separator и другие.
  4. Определите функцию для показа контекстного меню, которая будет вызываться при правом клике мыши.
  5. Привяжите это контекстное меню к виджету или к главному окну с помощью метода bind на событие <Button-3> (правая кнопка мыши).

Пример создания контекстного меню:

import tkinter as tk

def show_context_menu(event):
    try:
        # Показать контекстное меню
        context_menu.tk_popup(event.x_root, event.y_root)
    finally:
        # Обязательно нужно освободить ресурсы, закрыв меню
        context_menu.grab_release()

# Создание главного окна
root = tk.Tk()
root.title("Tkinter Context Menu Example")
root.geometry("400x250")

# Создание контекстного меню
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Copy", command=lambda: print("Copy"))
context_menu.add_command(label="Cut", command=lambda: print("Cut"))
context_menu.add_command(label="Paste", command=lambda: print("Paste"))
context_menu.add_separator()
context_menu.add_command(label="Refresh", command=lambda: print("Refresh"))

# Привязка контекстного меню к главному окну
root.bind("<Button-3>", show_context_menu)

# Запуск главного цикла приложения
root.mainloop()

tkinter context menu

Объяснение кода:

  • context_menu = tk.Menu(root, tearoff=0): Создаёт контекстное меню. tearoff=0 убирает возможность «оторвать» меню от окна.
  • show_context_menu(event): Функция для отображения контекстного меню. tk_popup(event.x_root, event.y_root) показывает меню в позиции курсора мыши.
  • root.bind("<Button-3>", show_context_menu): Привязывает функцию show_context_menu к событию <Button-3>, которое соответствует нажатию правой кнопки мыши.

Обработка различных виджетов

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

text_widget = tk.Text(root, height=10, width=40)
text_widget.pack(pady=20)

# Привязка контекстного меню к Text виджету
text_widget.bind("<Button-3>", show_context_menu)

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

Диалоговые окна в Tkinter

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

Встроенные диалоговые окна в Tkinter

Давайте рассмотрим основные типы диалоговых окон, доступных в Tkinter:

  1. messagebox — окна для отображения информационных сообщений и предупреждений.
  2. filedialog — окна для выбора файлов и директорий.
  3. colorchooser — окно для выбора цвета.

messagebox — информационные и предупреждающие сообщения

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

Основные функции messagebox:

  • showinfo(title, message) — показывает информационное сообщение.
  • showwarning(title, message) — показывает предупреждение.
  • showerror(title, message) — показывает сообщение об ошибке.
  • askquestion(title, message) — задаёт вопрос с ответами «yes» или «no».
  • askyesno(title, message) — аналогично askquestion, но возвращает True (Да) или False (Нет).
  • askokcancel(title, message) — запрашивает подтверждение (OK или Cancel) и возвращает True или False.
  • askretrycancel(title, message) — предлагает повторить попытку или отменить действие.

Пример использования messagebox:

import tkinter as tk
from tkinter import messagebox

# Функция для показа различных сообщений
def show_messages():
    messagebox.showinfo("Info", "Пример информационного сообщения")
    messagebox.showwarning("Warning", "Пример предупреждения")
    messagebox.showerror("Error", "Пример сообщения об ошибке")
    
    response = messagebox.askyesno("Question", "Вы хотите продолжить?")
    print("Ответ:", response)

# Создание основного окна
root = tk.Tk()
root.title("Messagebox Example")
root.geometry("300x200")

# Кнопка для вызова функции показа сообщений
button = tk.Button(root, text="Показать сообщения", command=show_messages)
button.pack(pady=20)

root.mainloop()

Это слайд-шоу требует JavaScript.

filedialog — диалоги выбора файлов

Модуль filedialog используется для отображения диалоговых окон, которые позволяют пользователю выбрать файлы или директории для открытия или сохранения.

Основные функции filedialog:

  • askopenfilename() — показывает диалог выбора файла для открытия.
  • askopenfilenames() — позволяет выбрать несколько файлов.
  • asksaveasfilename() — показывает диалог выбора файла для сохранения.
  • askopenfile() — возвращает открытый файловый объект из выбранного файла.
  • askopenfiles() — возвращает список открытых файловых объектов.
  • askdirectory() — показывает диалог выбора директории.

Пример использования filedialog:

import tkinter as tk
from tkinter import filedialog

# Функция для выбора файла
def open_file():
    filepath = filedialog.askopenfilename(title="Выберите файл")
    if filepath:
        print("Выбран файл:", filepath)

# Функция для выбора директории
def open_directory():
    directory = filedialog.askdirectory(title="Выберите папку")
    if directory:
        print("Выбрана директория:", directory)

# Создание основного окна
root = tk.Tk()
root.title("Filedialog Example")
root.geometry("300x200")

# Кнопки для вызова функций выбора файла и директории
button_file = tk.Button(root, text="Открыть файл", command=open_file)
button_file.pack(pady=10)

button_dir = tk.Button(root, text="Открыть директорию", command=open_directory)
button_dir.pack(pady=10)

root.mainloop()

tkinter filedialog

colorchooser — диалог выбора цвета

Модуль colorchooser предоставляет диалоговое окно для выбора цвета. Это позволяет пользователю выбрать цвет и получить его значение для использования в приложении.

Основная функция colorchooser:

  • askcolor() — показывает диалог выбора цвета и возвращает кортеж, содержащий RGB и шестнадцатеричное представление выбранного цвета.

Пример использования colorchooser:

import tkinter as tk
from tkinter import colorchooser

# Функция для выбора цвета
def choose_color():
    color = colorchooser.askcolor(title="Выберите цвет")
    if color:
        print("Выбран цвет:", color)
        # Пример изменения фона основного окна
        root.configure(background=color[1])

# Создание основного окна
root = tk.Tk()
root.title("Colorchooser Example")
root.geometry("300x200")

# Кнопка для вызова функции выбора цвета
button_color = tk.Button(root, text="Выбрать цвет", command=choose_color)
button_color.pack(pady=20)

root.mainloop()

tkinter colochooser

Работа с изображениями и иконками в Tkinter

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

Поддержка форматов изображений

Tkinter поддерживает работу с несколькими форматами изображений:

  • Bitmap (*.bmp): простой формат изображений, встроенный в Tkinter.
  • PhotoImage (*.gif*.pgm*.ppm): это класс Tkinter, который поддерживает эти форматы напрямую без дополнительных библиотек.
  • PNGJPEG, и другие форматы: для работы с такими форматами изображений рекомендуется использовать дополнительный модуль PIL / Pillow.

Для работы с широким спектром форматов, включая популярные PNG и JPEG, вы можете использовать библиотеку Pillow (PIL Fork). Pillow расширяет возможности Tkinter, добавляя поддержку многих форматов.

Отображение изображений на виджетах

Для отображения изображений на виджетах, таких как Label или Button, можно использовать PhotoImage или Image из Pillow.

Пример с PhotoImage

import tkinter as tk

# Создание основного окна
root = tk.Tk()
root.title("Отображение изображений")
root.geometry("300x200")

# Загрузка и отображение GIF изображения
try:
    photo = tk.PhotoImage(file="example.gif")
    label = tk.Label(root, image=photo)
    label.image = photo  # Сохраняем ссылку на изображение
    label.pack(pady=20)
except tk.TclError:
    print("Не удалось загрузить изображение.")

root.mainloop()

tkinter pic

Пример с Pillow (PNG, JPEG и другие форматы)

import tkinter as tk
from PIL import Image, ImageTk

# Создание основного окна
root = tk.Tk()
root.title("Отображение PNG изображения")
root.geometry("300x200")

# Загрузка и отображение PNG изображения с помощью Pillow
try:
    image = Image.open("example.png")
    photo = ImageTk.PhotoImage(image)
    
    label = tk.Label(root, image=photo)
    label.image = photo  # Сохраняем ссылку на изображение
    label.pack(pady=20)
except FileNotFoundError:
    print("Файл изображения не найден.")

root.mainloop()

tkinter pillow

Установка иконок для окон

Иконка окна — это маленькое изображение, которое отображается в заголовке окна и на панели задач операционной системы. Установка иконки помогает пользователю легче идентифицировать ваше приложение.

Для установки иконки окна используется метод iconbitmap или iconphoto.

Пример установки иконки с iconbitmap

import tkinter as tk

# Создание основного окна
root = tk.Tk()
root.title("Установка иконки окна")
root.geometry("300x200")

# Установка иконки окна
try:
    root.iconbitmap("icon.ico")
except tk.TclError:
    print("Не удалось загрузить иконку.")

root.mainloop()

Пример установки иконки с iconphoto

Использование iconphoto предпочтительнее для PNG изображений и для совместимости с разными ОС:

import tkinter as tk
from PIL import Image, ImageTk

# Создание основного окна
root = tk.Tk()
root.title("Установка PNG иконки окна")
root.geometry("300x200")

# Установка PNG иконки окна с помощью Pillow
try:
    image = Image.open("icon.png")
    photo = ImageTk.PhotoImage(image)
    root.iconphoto(False, photo)
except FileNotFoundError:
    print("Файл иконки не найден.")

root.mainloop()

tkinter icon

Практическое задание: Разработка простого графического редактора

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

Задачи:

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

Технические детали:

  • Используйте Canvas для рисования.
  • Используйте colorchooser для выбора цвета.
  • Используйте filedialog для открытия и сохранения файлов.
  • Используйте messagebox для вывода информационных сообщений.

Сценарий работы программы:

  1. Интерфейс: Главное окно с меню, холстом для рисования и возможностью выбора цвета.
  2. Меню:
    • File:
      • New — создает новый чистый холст.
      • Open — открывает изображение из файла и отображает его на холсте.
      • Save — сохраняет содержимое холста в файл.
      • Exit — выход из приложения.
    • Edit:
      • Choose Color — открывает диалог выбора цвета для рисования.
    • Help:
      • About — показывает информационное сообщение о программе.
  3. Рисование: Пользователи могут рисовать на холсте, выбирая цвет.

Пример кода:

import tkinter as tk
from tkinter import colorchooser, filedialog, messagebox
from PIL import Image, ImageTk, ImageDraw

def new_canvas():
    global image, draw, canvas
    canvas.delete("all")
    image = Image.new("RGB", (400, 400), "white")
    draw = ImageDraw.Draw(image)
    update_canvas_image()

def open_image():
    global image, draw, canvas
    filepath = filedialog.askopenfilename()
    if filepath:
        image = Image.open(filepath)
        draw = ImageDraw.Draw(image)
        update_canvas_image()

def save_image():
    filepath = filedialog.asksaveasfilename(defaultextension=".*", filetypes=[("PNG files", "*.png"), ("JPEG files", "*.jpg"), ("All files", "*.*")])
    if filepath:
        image.save(filepath)

def choose_color():
    global color
    color = colorchooser.askcolor(color)[1]

def about():
    messagebox.showinfo("About", "Simple Tkinter Paint Program\nBy ChatGPT")

def paint(event):
    x1, y1 = (event.x - 1), (event.y - 1)
    x2, y2 = (event.x + 1), (event.y + 1)
    canvas.create_oval(x1, y1, x2, y2, fill=color, outline=color)
    draw.ellipse([x1, y1, x2, y2], fill=color, outline=color)

def update_canvas_image():
    global canvas_image, photo
    photo = ImageTk.PhotoImage(image)
    canvas_image = canvas.create_image(0, 0, image=photo, anchor="nw")

# Main window
root = tk.Tk()
root.title("Simple Paint")
root.geometry("400x400")

# Default drawing color
color = "black"

# Create a canvas
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()

# Bind mouse motion to a paint function
canvas.bind("<B1-Motion>", paint)

# Create a menu
menu = tk.Menu(root)
root.config(menu=menu)

# File menu
file_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="New", command=new_canvas)
file_menu.add_command(label="Open...", command=open_image)
file_menu.add_command(label="Save", command=save_image)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=root.quit)

# Edit menu
edit_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="Edit", menu=edit_menu)
edit_menu.add_command(label="Choose Color", command=choose_color)

# Help menu
help_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="About", command=about)

# Initialize image
image = Image.new("RGB", (400, 400), "white")
draw = ImageDraw.Draw(image)

# Initialize canvas image
update_canvas_image()

# Run the main loop
root.mainloop()

Задание:

  1. Запустите данный код. Он представляет собой базовую структуру приложения.
  2. Добавьте иконку приложения используя метод iconphoto.
  3. Протестируйте все функции: Создание нового холста, открытие изображения, сохранение изображения, выбор цвета и рисование.
  4. Дополнительно: Улучшите интерфейс, добавив инструменты выбора толщины линии или другие улучшения по вашему выбору.

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

Виджеты для продвинутых пользователей

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

Treeview — для отображения иерархических данных

Treeview — это виджет, который используется для отображения иерархических данных, структурированных в виде дерева. Он может быть использован для представления файловой системы, JSON данных или любой другой вложенной структуры.

Основные возможности Treeview:

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

Пример использования Treeview:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Treeview")
root.geometry("300x200")

# Создание Treeview
tree = ttk.Treeview(root)

# Добавление колонок
tree["columns"] = ("one", "two")
tree.column("#0", width=100, minwidth=50)
tree.column("one", width=100, minwidth=50)
tree.column("two", width=100, minwidth=50)

tree.heading("#0", text="Name", anchor=tk.W)
tree.heading("one", text="Type", anchor=tk.W)
tree.heading("two", text="Description", anchor=tk.W)

# Добавление элементов
root_node = tree.insert("", 1, text="Folder", values=("Directory", "Root folder"))
tree.insert("", 2, text="File", values=("File", "Description of file"))

tree.insert(root_node, "end", text="Subfile", values=("File", "Child of root"))

tree.pack(expand=True, fill="both")
root.mainloop()

tkinter treeview

Notebook — для создания вкладок

Notebook — виджет, который позволяет организовать содержимое интерфейса в виде вкладок. Это очень удобно, когда в приложении есть категоризированные настройки или разделы.

Основные возможности Notebook:

  • Создание вкладок для организации интерфейса.
  • Переключение между вкладками с сохранением содержимого каждой из них.
  • Поддержка добавления и удаления вкладок динамически.

Пример использования Notebook:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Notebook")
root.geometry("300x200")

# Создание виджета Notebook
notebook = ttk.Notebook(root)

# Создание двух вкладок
tab1 = ttk.Frame(notebook)
tab2 = ttk.Frame(notebook)

notebook.add(tab1, text='Вкладка 1')
notebook.add(tab2, text='Вкладка 2')

# Добавление содержимого в первую вкладку
ttk.Label(tab1, text="Содержимое первой вкладки").pack(padx=10, pady=10)

# Добавление содержимого во вторую вкладку
ttk.Label(tab2, text="Содержимое второй вкладки").pack(padx=10, pady=10)

notebook.pack(expand=True, fill="both")

root.mainloop()

tkinter notebook

Scrollbar — для добавления прокрутки

Scrollbar — это виджет, который добавляется к другим виджетам, таким как ListboxText, и Canvas, для предоставления возможности прокрутки содержимого.

Основные возможности Scrollbar:

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

Пример использования Scrollbar с Listbox:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Scrollbar")
root.geometry("200x150")

# Создание фрейма для упаковки Listbox и Scrollbar
frame = ttk.Frame(root)
frame.pack(expand=True, fill="both")

# Создание Scrollbar
scrollbar = ttk.Scrollbar(frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

# Создание Listbox
listbox = tk.Listbox(frame, yscrollcommand=scrollbar.set)
for i in range(100):
    listbox.insert(tk.END, f"Элемент {i}")

listbox.pack(side=tk.LEFT, fill="both", expand=True)

# Связывание Scrollbar с Listbox
scrollbar.config(command=listbox.yview)

root.mainloop()

tkinter listbox

Группы радиокнопок (RadioButton):

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

Пример использования радиокнопок:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Radiobutton")
root.geometry("200x150")

# Переменная для хранения выбранного значения
selected_option = tk.StringVar()

# Функция для отображения выбранного значения
def show_choice():
    print(selected_option.get())

# Создание радиокнопок
ttk.Radiobutton(root, text="Опция 1", variable=selected_option, value="1", command=show_choice).pack()
ttk.Radiobutton(root, text="Опция 2", variable=selected_option, value="2", command=show_choice).pack()

root.mainloop()

tkinter radiobutton

Чекбоксы (Checkbutton):

Чекбоксы позволяют выбрать любое количество опций из доступных. Каждый чекбокс связан с своей собственной переменной типа BooleanVar.

Пример использования чекбоксов:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Checkbutton")
root.geometry("200x150")

# Переменные для хранения состояния чекбоксов
check1 = tk.BooleanVar()
check2 = tk.BooleanVar()

# Функция для отображения состояния чекбоксов
def show_checked():
    print("Чекбокс 1:", check1.get())
    print("Чекбокс 2:", check2.get())

ttk.Checkbutton(root, text="Чекбокс 1", variable=check1, command=show_checked).pack()
ttk.Checkbutton(root, text="Чекбокс 2", variable=check2, command=show_checked).pack()

root.mainloop()

tkinter checkbox

Комбобоксы (Combobox):

Combobox — это выпадающий список с возможностью ввода текста пользователем.

Пример использования Combobox:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Combobox")
root.geometry("200x150")

# Создание Combobox
selected_value = tk.StringVar()
combobox = ttk.Combobox(root, textvariable=selected_value)
combobox['values'] = ("Опция 1", "Опция 2", "Опция 3")
combobox.pack(pady=5)

# Функция для отображения выбранного значения
def show_value():
    print("Выбрано:", selected_value.get())

ttk.Button(root, text="Получить значение", command=show_value).pack(pady=5)

root.mainloop()

tkinter checkbox

Листбоксы (Listbox)

Listbox — это виджет, который отображает список элементов, из которого пользователь может выбрать один или несколько элементов. Этот виджет часто используется для представления списка опций или для отображения данных в простом вертикальном массиве.

Основные возможности Listbox:

  • Поддержка одиночного и множественного выбора.
  • Возможность прокрутки списка при большом количестве элементов.
  • Легкое получение выбранного элемента или элементов.

Пример использования Listbox:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Пример Listbox")
root.geometry("300x200")

# Создание Listbox
listbox = tk.Listbox(root)
listbox.pack(padx=20, pady=20, fill="both", expand=True)

# Добавление элементов в Listbox
for i in range(1, 21):
    listbox.insert(tk.END, f"Элемент {i}")

# Функция для вывода выбранного элемента
def show_selected():
    selection = listbox.curselection()
    for i in selection:
        print(listbox.get(i))

# Кнопка для вывода выбранного элемента
button = tk.Button(root, text="Показать выбранный", command=show_selected)
button.pack(pady=10)

root.mainloop()

tkinter listbox2

Примеры приложений на tkinter

Текстовый редактор

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

Шаги для создания текстового редактора:

  1. Импорт необходимых модулей: Нам нужно импортировать tkinter для GUI и tkinter.filedialog для работы с файловой системой.
  2. Создание основного окна: Инициализируем главное окно приложения.
  3. Добавление виджетов: Добавим Text виджет для редактирования текста и Menu для создания меню.
  4. Функции для работы с файлами: Напишем функции для создания нового файла, открытия существующего файла, сохранения файла и выхода из приложения.
  5. Привязка функций к меню: Соединим функции с соответствующими пунктами меню.

tkinter текстовый редактор

Пример кода

Ниже представлен полный код простого текстового редактора:

import tkinter as tk
from tkinter import scrolledtext
from tkinter import messagebox
from tkinter import filedialog

class TextEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("Простой текстовый редактор")

        # Инициализация имени файла
        self.current_file = None

        # Создание текстового поля с прокруткой
        self.text_area = scrolledtext.ScrolledText(self.root, wrap=tk.WORD, font=("Arial", 12))
        self.text_area.pack(fill=tk.BOTH, expand=True)

        # Создание меню
        self.menu = tk.Menu(self.root)
        self.root.config(menu=self.menu)

        # Добавление пунктов меню
        self.file_menu = tk.Menu(self.menu, tearoff=0)
        self.menu.add_cascade(label="Файл", menu=self.file_menu)
        self.file_menu.add_command(label="Новый", command=self.new_file)
        self.file_menu.add_command(label="Открыть...", command=self.open_file)
        self.file_menu.add_command(label="Сохранить", command=self.save_file)
        self.file_menu.add_command(label="Сохранить как...", command=self.save_as_file)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="Выход", command=self.exit_editor)

    def new_file(self):
        """ Создать новый файл """
        self.text_area.delete(1.0, tk.END)
        self.current_file = None
        self.root.title("Новый файл - Простой текстовый редактор")

    def open_file(self):
        """ Открыть существующий файл """
        file_path = filedialog.askopenfilename(defaultextension=".txt",
                                               filetypes=[("Текстовые документы", "*.txt"), ("Все файлы", "*.*")])
        if file_path:
            with open(file_path, 'r', encoding='utf-8') as file:
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(1.0, file.read())
            self.current_file = file_path
            self.root.title(f"{self.current_file} - Простой текстовый редактор")

    def save_file(self):
        """ Сохранить текущий файл """
        if not self.current_file:
            self.save_as_file()
        else:
            with open(self.current_file, 'w', encoding='utf-8') as file:
                file.write(self.text_area.get(1.0, tk.END))
            self.root.title(f"{self.current_file} - Простой текстовый редактор")

    def save_as_file(self):
        """ Сохранить текущий файл под новым именем """
        file_path = filedialog.asksaveasfilename(defaultextension=".txt",
                                                 filetypes=[("Текстовые документы", "*.txt"), ("Все файлы", "*.*")])
        if file_path:
            with open(file_path, 'w', encoding='utf-8') as file:
                file.write(self.text_area.get(1.0, tk.END))
            self.current_file = file_path
            self.root.title(f"{self.current_file} - Простой текстовый редактор")

    def exit_editor(self):
        """ Выход из приложения """
        if messagebox.askokcancel("Выход", "Вы уверены, что хотите выйти?"):
            self.root.quit()

# Создание главного окна
root = tk.Tk()
root.geometry("800x600")

app = TextEditor(root)

# Запустить главный цикл приложения
root.mainloop()

Объяснение кода

  • Класс TextEditor: Это основной класс нашего приложения. В нем определены все методы для работы с текстовым редактором и его меню.
  • Методы new_fileopen_filesave_filesave_as_file: Эти методы реализуют основную функциональность редактора — создание новых файлов, открытие, сохранение и «сохранение как» для файлов.
  • Метод exit_editor: Этот метод проверяет, хочет ли пользователь действительно выйти из приложения.
  • Виджет scrolledtext.ScrolledText: Используется для создания области с редактируемым текстом и встроенной прокруткой.
  • Меню: Создается через Menu виджеты, и к ним привязываются соответствующие функции.

Запуск приложения

Чтобы запустить этот текстовый редактор, сохраните код в файл с расширением .py, например, text_editor.py, и запустите его через интерпретатор Python. В результате у вас откроется окно приложения, где можно создавать, редактировать, сохранять и открывать текстовые файлы.

Калькулятор

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

Основные шаги для создания калькулятора:

  1. Импорт необходимых модулей: Нам понадобится только tkinter.
  2. Создание основного окна: Инициализируем главное окно приложения.
  3. Добавление виджетов: Добавим виджет Entry для отображения ввода/вывода и кнопки для цифр и операций.
  4. Логика калькулятора: Напишем функции для обработки нажатий на кнопки (ввод чисел и операций, вычисление результата).

tkinter калькулятор

Пример кода

Ниже представлен полный код калькулятора:

import tkinter as tk

class Calculator:
    def __init__(self, root):
        self.root = root
        self.root.title("Калькулятор")

        # Инициализация переменной, содержащей текущее значение в дисплее калькулятора
        self.current_input = ""
        self.display = tk.Entry(self.root, width=40, borderwidth=3, font=("Arial", 16))
        self.display.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

        # Кнопки для калькулятора
        buttons = [
            '7', '8', '9', '+',
            '4', '5', '6', '-',
            '1', '2', '3', '*',
            'C', '0', '=', '/'
        ]

        # Размещение кнопок на сетке
        row = 1
        col = 0
        for i, button in enumerate(buttons):
            if i % 4 == 0 and i != 0:
                row += 1
                col = 0
            tk.Button(self.root, text=button, width=9, height=3, font=("Arial", 16), 
                      command=lambda b=button: self.button_click(b)).grid(row=row, column=col)
            col += 1

    def button_click(self, button):
        if button == 'C':
            # Очистка дисплея
            self.current_input = ""
            self.display.delete(0, tk.END)
        elif button == '=':
            # Вычисление выражения
            try:
                self.current_input = str(eval(self.current_input))
                self.display.delete(0, tk.END)
                self.display.insert(0, self.current_input)
            except Exception as e:
                self.display.delete(0, tk.END)
                self.display.insert(0, "Ошибка")
                self.current_input = ""
        else:
            # Добавление символа к текущему вводу и обновление дисплея
            self.current_input += str(button)
            self.display.delete(0, tk.END)
            self.display.insert(0, self.current_input)

# Создание главного окна
root = tk.Tk()
root.geometry("360x380")

app = Calculator(root)

# Запустить главный цикл приложения
root.mainloop()

Объяснение кода

  • Класс Calculator: Это основной класс нашего приложения. В нем определены все методы для работы с калькулятором.
  • Метод button_click: Этот метод обрабатывает нажатия на все кнопки калькулятора. Он обновляет дисплей или вычисляет результат в зависимости от нажатой кнопки.
  • Виджет Entry: Используется для отображения текущего ввода или результата.
  • Сетка кнопок: Кнопки расположены на сетке с помощью метода .grid(), что позволяет удобно их организовать.

Запуск приложения

Для запуска калькулятора сохраните код в файл, например calculator.py, и запустите его через интерпретатор Python. После запуска у вас откроется окно калькулятора, где можно вводить числа и операции, а также получать результаты вычислений.

Просмотрщик изображений

Давайте создадим приложение для просмотра изображений на Python с использованием Tkinter. Это приложение будет позволять пользователю загружать изображения из файла и перемещаться между ними в папке.

Основные шаги для создания приложения для просмотра изображений:

  1. Импорт необходимых модулей: Нам понадобится tkinter для создания GUI, PIL (Python Imaging Library) для работы с изображениями и os для работы с файловой системой.
  2. Создание основного окна: Инициализация главного окна приложения.
  3. Добавление виджетов: Создание кнопок для навигации и места для отображения изображений.
  4. Функции для работы с изображениями: Напишем функции для загрузки изображений, перехода к следующему/предыдущему изображению.

Пример кода

Ниже представлен полный код приложения для просмотра изображений:

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
import os

class ImageViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("Просмотрщик изображений")

        # Список изображений и текущий индекс
        self.images = []
        self.current_image_index = 0

        # Фрейм для кнопок управления
        control_frame = tk.Frame(self.root)
        control_frame.pack(side=tk.BOTTOM, pady=(5, 15))

        # Кнопка "Открыть папку"
        self.btn_open = tk.Button(control_frame, text="Открыть папку", command=self.open_folder)
        self.btn_open.pack(side=tk.LEFT, padx=(10, 20))

        # Кнопка "Предыдущее изображение"
        self.btn_prev = tk.Button(control_frame, text="<< Пред.", command=self.show_prev_image)
        self.btn_prev.pack(side=tk.LEFT, padx=10)

        # Кнопка "Следующее изображение"
        self.btn_next = tk.Button(control_frame, text="След. >>", command=self.show_next_image)
        self.btn_next.pack(side=tk.LEFT, padx=10)

        # Место для отображения изображения
        self.image_label = tk.Label(self.root)
        self.image_label.pack(padx=10, pady=10)

    def open_folder(self):
        """ Открыть папку с изображениями """
        folder_path = filedialog.askdirectory()
        if not folder_path:
            return

        # Поддерживаемые форматы изображений
        extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
        self.images = [os.path.join(folder_path, f) for f in os.listdir(folder_path)
                       if os.path.splitext(f)[1].lower() in extensions]

        if not self.images:
            self.show_message("Изображения не найдены")
            return

        self.current_image_index = 0
        self.show_image()

    def show_image(self):
        """ Отобразить текущее изображение """
        if not self.images:
            self.show_message("Изображения не загружены")
            return

        image_path = self.images[self.current_image_index]
        img = Image.open(image_path)

        # Масштабирование изображения для подгонки под размер окна
        img = self.resize_image(img)

        # Конвертация изображения в формат Tkinter
        self.tk_image = ImageTk.PhotoImage(img)
        self.image_label.config(image=self.tk_image)

    def resize_image(self, img):
        """ Масштабировать изображение, сохраняя пропорции """
        max_height = self.root.winfo_height() - 100
        max_width = self.root.winfo_width() - 50

        original_width, original_height = img.size

        ratio = min(max_width / original_width, max_height / original_height)
        new_width = int(original_width * ratio)
        new_height = int(original_height * ratio)

        return img.resize((new_width, new_height), Image.ANTIALIAS)

    def show_prev_image(self):
        """ Показать предыдущее изображение """
        if not self.images:
            self.show_message("Изображения не загружены")
            return

        self.current_image_index = (self.current_image_index - 1) % len(self.images)
        self.show_image()

    def show_next_image(self):
        """ Показать следующее изображение """
        if not self.images:
            self.show_message("Изображения не загружены")
            return

        self.current_image_index = (self.current_image_index + 1) % len(self.images)
        self.show_image()

    def show_message(self, message):
        """ Показать сообщение вместо изображения """
        self.image_label.config(text=message, image='')

# Создание главного окна
root = tk.Tk()
root.geometry("800x600")

app = ImageViewer(root)

# Запустить главный цикл приложения
root.mainloop()

Объяснение кода

  • Класс ImageViewer: Основной класс приложения, содержащий логику работы с изображениями и интерфейсом.
  • Методы open_foldershow_imageshow_prev_imageshow_next_image: Методы для загрузки изображений из папки, отображения текущего изображения, перехода к следующему и предыдущему изображению.
  • Метод resize_image: Адаптирует размер изображения для его корректного отображения в окне приложения, сохраняя пропорции.
  • Виджеты Button и Label: Используются для создания кнопок управления и места для отображения изображений.

Запуск приложения

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

Лучшие практики при работе с Tkinter

1. Структурирование кода:

  • Используйте классы для организации кода: Создание GUI приложений с помощью классов помогает управлять состоянием и поведением интерфейса. Каждый виджет и его логика обработки событий должны быть инкапсулированы в соответствующих классах.
    class MyApp:
        def __init__(self, root):
            self.root = root
            self.initialize_ui()
    
        def initialize_ui(self):
            tk.Label(self.root, text="Привет, Tkinter!").pack()
  • Разделяйте логику и интерфейс: Постарайтесь разделить код, который управляет логикой приложения (например, обработка данных), от кода, который управляет отображением (GUI элементы).

2. Управление ресурсами:

  • Избегайте загрузки больших ресурсов в основной поток: Если ваше приложение загружает большие изображения или выполняет тяжелые вычисления, рассмотрите возможность использования потоков (threading) или асинхронного программирования для предотвращения «зависания» интерфейса.
    import threading
    
    def load_large_image():
        # Загружаем большое изображение
        pass
    
    thread = threading.Thread(target=load_large_image)
    thread.start()

3. Работа с виджетами:

  • Используйте grid и pack разумно: Не используйте grid и pack в одном и том же контейнере. Это может привести к ошибкам. Выберите один метод геометрии и используйте его консистентно.
  • Обновление виджетов: Используйте метод .update_idletasks() для обновления состояния виджетов перед запуском длительных операций в том же потоке, чтобы интерфейс не «замерзал».
    self.root.update_idletasks()

4. Производительность:

  • Избегайте лишних обновлений интерфейса: Если вам не нужно каждый раз обновлять интерфейс при изменении данных, не делайте этого. Отложенное (lazy) обновление может улучшить производительность.
  • Минимизируйте использование after для таймеров: Используйте этот метод для периодических задач, но не злоупотребляйте им, чтобы избежать ненужной нагрузки на процессор.
    self.root.after(1000, self.update_clock)

5. Обработка ошибок и исключений:

  • Используйте блоки try / except для обработки исключений: Это поможет избежать аварийного завершения программы при возникновении ошибок.
    try:
        # Попытка выполнить опасную операцию
        result = 10 / 0
    except ZeroDivisionError:
        print("На ноль делить нельзя!")
  • Логгирование ошибок: Вместо вывода ошибок прямо в консоль или интерфейс пользователя рассмотрите возможность логгирования для удобства отладки.
    import logging
    logging.basicConfig(level=logging.INFO)
    logging.error("Это сообщение об ошибке")
  • Информирование пользователя: В случае ошибок в GUI приложениях информируйте пользователя с помощью диалоговых окон.
    from tkinter import messagebox
    messagebox.showerror("Ошибка", "Что-то пошло не так!")

6. Работа с файлами и данными:

  • Используйте контекстные менеджеры для работы с файлами: Это упрощает чтение и запись файлов и автоматически управляет закрытием файла.
    with open("file.txt", "r") as file:
        data = file.read()
  • Безопасная работа с путями: Используйте модуль os.path для безопасной работы с путями файловой системы.
    import os
    full_path = os.path.join("folder_name", "file_name.txt")

Заключение

  • Читаемость кода: Пишите код так, чтобы его было легко читать и понимать. Используйте комментарии и документирование функций.
  • Рефакторинг: Не бойтесь рефакторить код для улучшения структуры и производительности.

Проектные работы по tkinter

Приложение «Планировщик задач» (To-Do List App)

Цель проекта: Создать приложение для управления ежедневными задачами.

Основные функции:

  • Добавление задачи: Пользователь может ввести задачу в текстовое поле и добавить её в список нажатием кнопки.
  • Удаление задачи: Пользователь может удалить выполненную задачу из списка.
  • Сохранение списка: Список задач должен сохраняться между сессиями работы приложения (используйте файлы для хранения данных).
  • Загрузка списка: При запуске приложения список задач загружается из сохранённого файла.

Конвертер валют

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

Основные функции:

  • Выбор валют: Пользователь может выбрать исходную и целевую валюту из выпадающего списка.
  • Ввод суммы: Пользователь вводит сумму для конвертации.
  • Отображение результата: Приложение показывает результат конвертации.
  • Получение актуальных курсов валют: Используйте API, например, ExchangeRate-API, для получения актуальных курсов.

Крестики-нолики с GUI

Цель проекта: Создать двухпользовательскую игру «Крестики-нолики» с графическим интерфейсом.

Основные функции:

  • Игровое поле: Отобразите игровое поле 3×3 с помощью Tkinter.
  • Ходы игроков: Игроки по очереди ставят на поле «X» и «O».
  • Определение победителя: Приложение должно определять победителя как только один из игроков собрал ряд из трёх символов.
  • Новая игра: Кнопка для начала новой игры после завершения предыдущей.

Приложение «Погодный информер»

Цель проекта: Создать приложение, которое показывает текущую погоду и прогноз на ближайшие дни для выбранного города.

Основные функции:

  • Выбор города: Пользователь может ввести название города в текстовое поле или выбрать его из списка.
  • Отображение погоды: Приложение отображает информацию о погоде: температуру, влажность, давление, скорость ветра, и погодные условия (солнечно, облачно, дождь и т.д.).
  • Прогноз на несколько дней: Показывает погоду на ближайшие дни.
  • Загрузка данных о погоде: Используйте внешний API, например, OpenWeatherMap API для получения данных о погоде.

Музыкальный плеер

Цель проекта: Создать простой музыкальный плеер с графическим интерфейсом, который может воспроизводить, останавливать и переключать аудиофайлы.

Основные функции:

  • Воспроизведение музыки: Пользователь может выбрать аудиофайл и воспроизвести его.
  • Пауза и продолжение воспроизведения: Пользователь может поставить трек на паузу и продолжить воспроизведение.
  • Выбор следующего/предыдущего трека: Переключение между треками в плейлисте.
  • Отображение списка треков: Показывать список доступных аудиофайлов.
  • Регулировка громкости: Пользователь может регулировать громкость воспроизведения.

Индивидуальное и групповое обучение «Python Junior»
Если вы хотите научиться программировать на Python, могу помочь. Запишитесь на мой курс «Python Junior» и начните свой путь в мир ИТ уже сегодня!

Контакты
Для получения дополнительной информации и записи на курсы свяжитесь со мной:

Телеграм: https://t.me/Vvkomlev
Email: victor.komlev@mail.ru

Объясняю сложное простыми словами. Даже если вы никогда не работали с ИТ и далеки от программирования, теперь у вас точно все получится! Проверено десятками примеров моих учеников.

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

Практическая направленность. 80%: практики, 20% теории. У меня множество авторских заданий, которые фокусируются на практике. Вы не просто изучаете теорию, а сразу применяете знания в реальных проектах и задачах.

Разнообразие учебных материалов: Теория представлена в виде текстовых уроков с примерами и видео, что делает обучение максимально эффективным и удобным.

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

Понравилась статья? Поделиться с друзьями:
Школа Виктора Комлева
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.