Создание GUI-календаря на Python с Tkinter

Создание календаря — удобный учебный проект для освоения разработки GUI-приложений на Python. Проект даёт базу для планировщика событий, простого трекера встреч и напоминаний, а также позволяет поработать с интерфейсными виджетами, макетами и обработкой времени.
Зачем этот проект
- Быстро получить практику с Tkinter и модулем calendar.
- Научиться управлять виджетами, макетами и событиями GUI.
- Получить каркас, который можно расширять до приложения с событиями и синхронизацией.
В следующих разделах мы разберём исходный код, улучшения, сценарии использования, тесты и рекомендации по локализации и безопасности.
Какие модули используются и почему
- calendar — стандартный модуль Python для работы с календарями и форматирования текстового представления месяца.
- tkinter — стандартная библиотека для создания простых кроссплатформенных GUI.
- datetime — работа с текущими датами и временем.
Краткое определение терминов
- Виджет — элемент интерфейса (кнопка, поле ввода, текст и т. п.).
- Spinbox — виджет для выбора числового значения из диапазона.
Пререквизиты перед запуском
- Python 3.7+ (Tkinter обычно идёт в комплекте; на некоторых дистрибутивах Linux пакет называется python3-tk).
- Терминал для запуска скрипта.
Примечание: pip install tkinter обычно не нужен и может не сработать — Tkinter поставляется с Python или устанавливается системным менеджером пакетов. На Linux используйте apt/yum/pacman, на Windows и macOS Tkinter уже встроен в стандартную сборку Python.
Important: Проверяйте доступность tkinter в вашей среде: в интерактивной оболочке выполните import tkinter; если ошибка, установите системный пакет.
Полный пример кода (улучшенная версия)
Ниже — собранный и немного улучшенный пример кода. Он сохраняет исходную логику: выбор месяца и года, генерация текстового календаря и базовые кнопки. Я добавил небольшие проверки ввода и комментарии.
from tkinter import *
import calendar
from datetime import date
import json
import os
# Опции сохранения событий (локально, файл JSON)
EVENTS_FILE = 'events.json'
# Загрузка/сохранение событий (простейшая реализация)
def load_events():
if os.path.exists(EVENTS_FILE):
with open(EVENTS_FILE, 'r', encoding='utf-8') as f:
try:
return json.load(f)
except Exception:
return {}
return {}
def save_events(events):
with open(EVENTS_FILE, 'w', encoding='utf-8') as f:
json.dump(events, f, ensure_ascii=False, indent=2)
# Основные функции интерфейса
def printCalendar():
try:
month = int(month_box.get())
year = int(year_box.get())
except ValueError:
calendar_field.delete('1.0', END)
calendar_field.insert(END, 'Некорректный ввод. Введите числа для месяца и года.')
return
# Формируем календарь с понедельника как первый день недели
cal = calendar.TextCalendar(firstweekday=0)
output_calendar = cal.formatmonth(year, month)
calendar_field.delete('1.0', END)
calendar_field.insert(END, output_calendar)
# Отобразить события, если есть
key = f"{year:04d}-{month:02d}"
if key in events:
calendar_field.insert(END, '\nСобытия:\n')
for day, items in sorted(events[key].items(), key=lambda x: int(x[0])):
for it in items:
calendar_field.insert(END, f"{day}: {it}\n")
def reset():
calendar_field.delete('1.0', END)
month_var.set(current_month)
year_var.set(current_year)
month_box.config(textvariable=month_var)
year_box.config(textvariable=year_var)
def close():
guiWindow.destroy()
# Простейший диалог добавления события
def add_event():
try:
day = int(event_day.get())
month = int(month_box.get())
year = int(year_box.get())
except ValueError:
return
key = f"{year:04d}-{month:02d}"
events.setdefault(key, {})
events[key].setdefault(str(day), []).append(event_text.get())
save_events(events)
event_text.set('')
printCalendar()
# Инициализация GUI
if __name__ == "__main__":
events = load_events()
guiWindow = Tk()
guiWindow.title("GUI Calendar")
guiWindow.geometry('560x650')
guiWindow.resizable(0, 0)
header_frame = Frame(guiWindow)
entry_frame = Frame(guiWindow)
result_frame = Frame(guiWindow)
button_frame = Frame(guiWindow)
event_frame = Frame(guiWindow)
header_frame.pack(expand=True, fill='both')
entry_frame.pack(expand=True, fill='both')
result_frame.pack(expand=True, fill='both')
button_frame.pack(expand=True, fill='both')
event_frame.pack(expand=True, fill='both')
header_label = Label(header_frame, text='КАЛЕНДАРЬ', font=('arial', 36, 'bold'), fg='#A020F0')
header_label.pack(expand=True, fill='both')
month_label = Label(entry_frame, text='Месяц:', font=('arial', 14, 'bold'))
year_label = Label(entry_frame, text='Год:', font=('arial', 14, 'bold'))
month_label.place(x=20, y=10)
year_label.place(x=260, y=10)
month_var = IntVar(entry_frame)
year_var = IntVar(entry_frame)
current_month = date.today().month
current_year = date.today().year
month_var.set(current_month)
year_var.set(current_year)
month_box = Spinbox(entry_frame, from_=1, to=12, width=8, textvariable=month_var, font=('arial', 12))
year_box = Spinbox(entry_frame, from_=1, to=3000, width=8, textvariable=year_var, font=('arial', 12))
month_box.place(x=90, y=10)
year_box.place(x=320, y=10)
calendar_field = Text(result_frame, width=40, height=12, font=('courier', 12), relief=RIDGE, borderwidth=2)
calendar_field.pack(padx=10, pady=10)
display_button = Button(button_frame, text='ОТОБРАЗИТЬ', bg='#A020F0', fg='#E0FFFF', command=printCalendar, font=('arial', 12))
reset_button = Button(button_frame, text='СБРОСИТЬ', bg='#A020F0', fg='#E0FFFF', command=reset, font=('arial', 12))
close_button = Button(button_frame, text='ЗАКРЫТЬ', bg='#A020F0', fg='#E0FFFF', command=close, font=('arial', 12))
display_button.place(x=80, y=10)
reset_button.place(x=220, y=10)
close_button.place(x=360, y=10)
# Простая область добавления событий
Label(event_frame, text='Добавить событие (день и текст):', font=('arial', 12)).place(x=10, y=6)
event_day = StringVar()
event_text = StringVar()
Entry(event_frame, textvariable=event_day, width=5).place(x=260, y=6)
Entry(event_frame, textvariable=event_text, width=30).place(x=300, y=6)
Button(event_frame, text='Добавить', command=add_event).place(x=520, y=3)
printCalendar()
guiWindow.mainloop()Notes: Этот код — стартовая точка. Он хранит события локально в events.json и показывает их под текстовым календарём. Для реального приложения хранение и синхронизация должны быть более продуманными (база данных, сервер, авторизация).
Пояснения к ключевым частям
- Инициализация Tk: Tk() создаёт главное окно. geometry устанавливает размер, resizable запрещает изменение окна пользователем.
- Frames: контейнеры (header_frame, entry_frame и т. д.) упрощают компоновку виджетов.
- Spinbox: удобен для выбора месяца и года; у него есть textvariable — IntVar/ StringVar.
- Text: используется для вывода текстового календаря; font=courier обеспечивает моноширинный вид.
- Кнопки: используют параметр command для привязки функций-обработчиков.
Локализация и формат недели
По умолчанию calendar.TextCalendar(firstweekday=0) считает понедельник первым днём недели (0 — понедельник). Для локалей, где неделя начинается в воскресенье, укажите firstweekday=6.
Если вы локализуете приложение для России, подумайте о форматах дат, названиях месяцев и о том, с какой стороны отображать дни недели (понедельник/воскресенье первым).
Расширения и идеи для следующих шагов
- Хранение событий в базе данных SQLite.
- Отправка локальных напоминаний (через sched/threads или системные уведомления).
- Синхронизация с внешними календарями (Google Calendar API) — потребует OAuth и серверной части.
- Улучшенный интерфейс: показывать сетку дат с кликабельными днями (Label/Button для каждого дня) вместо текстовой области.
- Экспорт/импорт событий в iCal/CSV.
Альтернативные подходы
- PyQt/PySide: для более сложных и нативных интерфейсов.
- Kivy: если нужно мобильное кроссплатформенное приложение.
- Веб-приложение (Flask/Django + React/Vue): удобнее для совместного использования и синхронизации.
Когда этот подход не подойдёт (контрпримеры)
- Если нужен сложный календарь с множеством интерактивных элементов и плавной анимацией — лучше PyQt или веб-технологии.
- Для многопользовательского доступа и надёжной синхронизации одного JSON-файла недостаточно.
- Для фоновых напоминаний при свёрнутом приложении (особенно на мобильных устройствах) потребуется системная интеграция.
Критерии приёмки
- Интерфейс открывается без ошибок на целевой платформе.
- При выборе месяца/года отображается соответствующий календарь.
- Кнопки “ОТОБРАЗИТЬ”, “СБРОСИТЬ” и “ЗАКРЫТЬ” работают корректно.
- Добавление события сохраняет данные в events.json и отображает их в разделе календаря.
- Поведение при некорректном вводе – информативное сообщение, приложение не падает.
Тесты и случаи приёмки
- TC1: Открытие приложения — окно отображается, нет исключений.
- TC2: Выбрать месяц=3, year=2000 -> отображается март 2000.
- TC3: Ввести нечисловое значение в поле года -> вывод сообщения об ошибке.
- TC4: Добавление события на существующий день -> событие сохраняется и отображается.
- TC5: Удалить/повредить events.json -> программа должна создать/восстановить файл без падения.
Чек-лист ролей (разработчик / тестировщик / пользователь)
Разработчик:
- Проверить совместимость с Python 3.7+.
- Обработать возможные исключения при работе с файлом событий.
- Добавить локализацию (строки, форматы дат).
Тестировщик:
- Прогнать тесты TC1–TC5 на целевых платформах.
- Проверить поведение при переполнении событий (100+ в одном месяце).
Пользователь:
- Убедиться, что текущий месяц по умолчанию совпадает с системной датой.
- Проверить добавление и просмотр событий.
Мини-методология разработки (быстрый план)
- Прототип: собрать минимально работающий UI (выбор месяца/года -> текстовое отображение).
- Итерация: добавить сохранение событий и простую форму ввода.
- Тестирование: покрыть критические сценарии (сохранение, восстановление, граничные значения).
- Улучшение: перейти к сеточному представлению, добавить экспорт/импорт и синхронизацию.
Безопасность и приватность
- Сохранение событий локально безопасно, но при синхронизации на сервер нужно обеспечить шифрование и аутентификацию.
- Для приложений с персональными данными продумайте политику хранения данных и соответствие требованиям местного законодательства о защите персональных данных (например, GDPR для пользователей ЕС).
Совместимость и миграция
- Tkinter присутствует в стандартной сборке CPython на Windows и macOS. На некоторых Linux-дистрибутивах требуется установить пакет python3-tk.
- Для портирования на мобильные устройства рассмотрите Kivy или веб-реализацию.
Производительность и надёжность
- Для текстового календаря производительность не проблема. Но при добавлении фоновых задач (синхронизация, уведомления) используйте потоки или asyncio и не блокируйте главный цикл Tkinter.
- Потенциальная проблема: длительные сетевые операции в обработчиках команд вызывают зависание интерфейса.
Примеры визуальной отладки и распространённые ошибки
- Пустой или нечитаемый текст в Text — проверьте font и encoding.
- Ошибки при чтении events.json — обработайте исключения и перезапишите файл безопасно.
Резюме и дальнейшие шаги
Создание GUI-календаря с Tkinter — отличный стартовый проект. Вы получите практику работы с виджетами, макетами и модулем calendar, а также каркас для расширения до планировщика событий и напоминаний.
Рекомендуемые следующие шаги:
- Перевести текстовое представление в интерактивную сетку дней.
- Добавить хранение в SQLite и механизм бэкапа.
- Реализовать синхронизацию с внешними календарями (опционально).
Если нужно, могу подготовить:
- версию с сеточной (календарной) версткой дат, где каждый день — кликабельная кнопка;
- пример синхронизации с Google Calendar (план действий и требуемые разрешения);
- шаблон проекта с тестами и CI/CD для автотестирования.
Important: этот материал служит учебной и практической инструкцией; перед внедрением в продакшен продумайте архитектуру хранения и синхронизации данных.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone