Гид по технологиям

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

6 min read Python GUI Обновлено 22 Dec 2025
GUI-календарь на Python с Tkinter
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.

Если вы локализуете приложение для России, подумайте о форматах дат, названиях месяцев и о том, с какой стороны отображать дни недели (понедельник/воскресенье первым).

Расширения и идеи для следующих шагов

  1. Хранение событий в базе данных SQLite.
  2. Отправка локальных напоминаний (через sched/threads или системные уведомления).
  3. Синхронизация с внешними календарями (Google Calendar API) — потребует OAuth и серверной части.
  4. Улучшенный интерфейс: показывать сетку дат с кликабельными днями (Label/Button для каждого дня) вместо текстовой области.
  5. Экспорт/импорт событий в 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+ в одном месяце).

Пользователь:

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

Мини-методология разработки (быстрый план)

  1. Прототип: собрать минимально работающий UI (выбор месяца/года -> текстовое отображение).
  2. Итерация: добавить сохранение событий и простую форму ввода.
  3. Тестирование: покрыть критические сценарии (сохранение, восстановление, граничные значения).
  4. Улучшение: перейти к сеточному представлению, добавить экспорт/импорт и синхронизацию.

Безопасность и приватность

  • Сохранение событий локально безопасно, но при синхронизации на сервер нужно обеспечить шифрование и аутентификацию.
  • Для приложений с персональными данными продумайте политику хранения данных и соответствие требованиям местного законодательства о защите персональных данных (например, GDPR для пользователей ЕС).

Совместимость и миграция

  • Tkinter присутствует в стандартной сборке CPython на Windows и macOS. На некоторых Linux-дистрибутивах требуется установить пакет python3-tk.
  • Для портирования на мобильные устройства рассмотрите Kivy или веб-реализацию.

Производительность и надёжность

  • Для текстового календаря производительность не проблема. Но при добавлении фоновых задач (синхронизация, уведомления) используйте потоки или asyncio и не блокируйте главный цикл Tkinter.
  • Потенциальная проблема: длительные сетевые операции в обработчиках команд вызывают зависание интерфейса.

Примеры визуальной отладки и распространённые ошибки

  • Пустой или нечитаемый текст в Text — проверьте font и encoding.
  • Ошибки при чтении events.json — обработайте исключения и перезапишите файл безопасно.

Выход программы: окно календаря с текущим месяцем и датой

Календарь за март 2000 с примером отображения

Резюме и дальнейшие шаги

Создание GUI-календаря с Tkinter — отличный стартовый проект. Вы получите практику работы с виджетами, макетами и модулем calendar, а также каркас для расширения до планировщика событий и напоминаний.

Рекомендуемые следующие шаги:

  • Перевести текстовое представление в интерактивную сетку дней.
  • Добавить хранение в SQLite и механизм бэкапа.
  • Реализовать синхронизацию с внешними календарями (опционально).

Если нужно, могу подготовить:

  • версию с сеточной (календарной) версткой дат, где каждый день — кликабельная кнопка;
  • пример синхронизации с Google Calendar (план действий и требуемые разрешения);
  • шаблон проекта с тестами и CI/CD для автотестирования.

Important: этот материал служит учебной и практической инструкцией; перед внедрением в продакшен продумайте архитектуру хранения и синхронизации данных.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство