Исходный код EJIO.ctk_extender.components.date_picker

## /ctk_extender/components/date_picker.py

import customtkinter as ctk
import datetime
import calendar
from typing import Any, Callable
from ..widget import Widget

__all__ = [
    'DatePicker',
]


[документация] class DatePicker(Widget): """ Интерактивный календарный виджет выбора даты. Полностью автономен, перерисовывает дни месяца на лету. """ __slots__ = ( '_selected_date', '_current_year', '_current_month', '_on_select_callback', '_header_label', '_days_frame', '_day_buttons' ) def __init__( self, parent: Any, initial_date: datetime.date | None = None, on_select: Callable[[datetime.date], None] | None = None, geometry: Any = None, **kwargs: Any ) -> None: self._selected_date: datetime.date = initial_date or datetime.date.today() self._current_year: int = self._selected_date.year self._current_month: int = self._selected_date.month self._on_select_callback: Callable[[datetime.date], None] | None = on_select self._header_label: ctk.CTkLabel | None = None self._days_frame: ctk.CTkFrame | None = None self._day_buttons: list[ctk.CTkButton] = [] super().__init__(parent, geometry, **kwargs)
[документация] def build(self) -> ctk.CTkFrame: # Главный бокс календаря cal_frame = ctk.CTkFrame(self.parent, border_width=1, border_color="#374151") # 1. Шапка управления: Кнопка Левее, Месяц/Год, Кнопка Правее nav_frame = ctk.CTkFrame(cal_frame, fg_color="transparent") nav_frame.pack(side="top", fill="x", padx=10, pady=5) ctk.CTkButton(nav_frame, text="<", width=28, command=lambda: self._change_month(-1)).pack(side="left") self._header_label = ctk.CTkLabel(nav_frame, text="", font=("Arial", 12, "bold")) self._header_label.pack(side="left", expand=True, fill="x") ctk.CTkButton(nav_frame, text=">", width=28, command=lambda: self._change_month(1)).pack(side="right") # 2. Строка дней недели (Пн, Вт, Ср...) week_frame = ctk.CTkFrame(cal_frame, fg_color="transparent") week_frame.pack(side="top", fill="x", padx=5) days_shorts = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] for idx, d_name in enumerate(days_shorts): lbl = ctk.CTkLabel(week_frame, text=d_name, font=("Arial", 10, "italic"), width=32) lbl.grid(row=0, column=idx, padx=2) # 3. Сетка дней месяца (Динамическая матрица grid) self._days_frame = ctk.CTkFrame(cal_frame, fg_color="transparent") self._days_frame.pack(side="top", fill="both", expand=True, padx=5, pady=(0, 5)) # Наполняем матрицу числами месяца self._render_calendar_days() return cal_frame
def _render_calendar_days(self) -> None: """Перерисовывает сетку кнопок дней для выбранного месяца и года.""" if not self._days_frame or not self._header_label: return # Чистим старые кнопки прошлых месяцев for btn in self._day_buttons: btn.destroy() self._day_buttons.clear() # Обновляем текст шапки (например, "Июнь 2026") months_names = ["", "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"] #TODO: добавить локализацию self._header_label.configure(text=f"{months_names[self._current_month]} {self._current_year}") # Получаем матрицу дней месяца через стандартный модуль calendar # Функция monthcalendar возвращает список недель, где дни вне месяца равны 0 cal_matrix = calendar.monthcalendar(self._current_year, self._current_month) for row_idx, week in enumerate(cal_matrix): for col_idx, day_num in enumerate(week): if day_num == 0: # Пустые дни по краям месяца забиваем невидимыми заглушками для сохранения сетки continue # Подсвечиваем выбранную дату другим цветом is_selected = ( day_num == self._selected_date.day and self._current_month == self._selected_date.month and self._current_year == self._selected_date.year ) bg_color = "#1D4ED8" if is_selected else ctk.ThemeManager.theme["CTkButton"]["fg_color"] # Создаем кнопку конкретного дня btn = ctk.CTkButton( self._days_frame, text=str(day_num), width=32, height=28, fg_color=bg_color, command=lambda d=day_num: self._select_day(d) ) btn.grid(row=row_idx, column=col_idx, padx=2, pady=2) self._day_buttons.append(btn) def _change_month(self, direction: int) -> None: """Сдвигает календарную страницу вперед или назад.""" self._current_month += direction if self._current_month > 12: self._current_month = 1 self._current_year += 1 elif self._current_month < 1: self._current_month = 12 self._current_year -= 1 self._render_calendar_days() def _select_day(self, day: int) -> None: """Фиксирует выбор даты пользователем.""" self._selected_date = datetime.date(self._current_year, self._current_month, day) self._render_calendar_days() # Перерисовываем для обновления подсветки кнопки if self._on_select_callback: try: self._on_select_callback(self._selected_date) except Exception: pass