Исходный код EJIO.ctk_extender.widget.form.base

## /ctk_extender/widget/form/base.py

import customtkinter as ctk
from typing import Any, Self
from loguru import logger

from EJIO.utils import List
from ..container import WidgetContainer
from ..base import Widget
from .fields import InputField
from .events import FormSubmittedEvent, FormValidationErrorEvent, FormReadyEvent
from ...event import EventController, on_event

__all__ = [
    'Form',
]


[документация] class Form(WidgetContainer): """ Специализированный контейнер для группировки элементов ввода. Автоматически собирает данные, проводит сквозную валидацию и очистку полей. """ def __init__( self, parent: ctk.CTk | ctk.CTkFrame | Any, geometry: Any = None ) -> None: # Хранилище только для элементов, реализующих протокол InputField self._fields: List[InputField] = List() self._is_submitting: bool = False super().__init__(parent, geometry)
[документация] def add_widget[W: Widget](self, widget_cls: type[W], *args: Any, **kwargs: Any) -> W: """ Переопределенный метод добавления виджета. Если добавляемый виджет является полем ввода, регистрирует его в форме. :param widget_cls: Класс создаваемого виджета (наследник Widget). :param args: Позиционные аргументы для конструктора виджета. :param kwargs: Именованные аргументы для конструктора виджета. :return: Созданный экземпляр виджета. """ widget = super().add_widget(widget_cls, *args, **kwargs) # Проверяем, реализует ли созданный виджет интерфейс InputField if isinstance(widget, InputField): self._fields.append(widget) logger.debug(f"Input Field {widget.name} successfully registered in form {self.__class__.__name__}.") return widget
[документация] def remove_widget[W: Widget](self, widget: W) -> Self: """ Удаляет виджет из формы, синхронно вычищая его из реестра полей ввода и каскадно уничтожая его ресурсы. :param widget: Экземпляр уничтожаемого виджета. """ if isinstance(widget, InputField) and widget in self._fields: self._fields.remove(widget) logger.debug(f"Input field {widget.name} removed from form {self.__class__.__name__} before destruction.") # Удаляем из базового списка детей контейнера и вызываем widget.destroy() super().remove_widget(widget) return self
@on_event(FormReadyEvent) def _on_form_ready(self, event: FormReadyEvent) -> None: """ Слушатель ответа от бизнес-логики: снимает блокировку с формы. Метод вызывается автоматически через контроллер событий. """ if event.target_form is self: self._is_submitting = False logger.info(f"Form {self.__class__.__name__} unlocked for input.")
[документация] def get_data(self) -> dict[str, Any]: """ Собирает текущие данные со всех зарегистрированных полей ввода. :return: Словарь, содержащий данные всех полей ввода в формате имя поля -> значение. """ data: dict[str, Any] = {} for field in self._fields: data[field.name] = field.get_value() return data
[документация] def set_data(self, data: dict[str, Any]) -> Self: """Заполняет поля формы переданным словарем данных.""" for field in self._fields: if field.name in data: field.set_value(data[field.name]) return self
[документация] def clear(self) -> Self: """Сбрасывает значения всех полей формы.""" for field in self._fields: field.clear() return self
[документация] def submit(self) -> bool: """ Запускает процесс валидации и отправки формы. Генерирует FormSubmittedEvent при успехе или FormValidationErrorEvent при ошибке. :return: True, если валидация успешна, иначе False. """ if self._is_submitting: logger.warning(f"Submission ignored: Form {self.__class__.__name__} is already being submitted.") return False self._is_submitting = True errors: dict[str, str] = {} # 1. Опрашиваем каждое поле на предмет внутренних ошибок for field in self._fields: error = field.validate() if error is not None: errors[field.name] = error # 2. Диспетчеризация результата if errors: logger.warning(f"Form {self.__class__.__name__} was not validated. Errors: {len(errors)}") EventController.emit(FormValidationErrorEvent(self, errors)) return False logger.info(f"Form {self.__class__.__name__} successfully validated and submitted.") EventController.emit(FormSubmittedEvent(self, self.get_data())) return True
[документация] def focus_field(self, name: str) -> None: """ Переводит фокус ввода на указанное поле формы по его имени. Безопасно извлекает внутренний инпут и запрашивает фокус у ОС. :param name: Имя поля на которое нужно переключиться. """ field = next((field for field in self._fields if field.name == name), None) if field is not None: try: field.focus() except Exception as e: logger.error(f"Failed to shift focus to the field. '{name}': {e}")
[документация] def destroy(self) -> None: """Очищает реестр полей ввода и каскадно уничтожает контейнер.""" if hasattr(self, '_fields') and self._fields is not None: try: self._fields.clear() except Exception: pass super().destroy()