Исходный код EJIO.ctk_extender.widget.container
## /ctk_extender/widget/container.py
import customtkinter as ctk
from typing import Any, Self
from loguru import logger
from EJIO.utils import List
from .base import Widget
from .geometry import GeometryStrategy
from .err import WidgetContainerExtensionError
__all__ = [
'WidgetContainer',
]
[документация]
class WidgetContainer(Widget):
"""
Компонент-контейнер, объединяющий несколько виджетов внутри единого фрейма (CTkFrame).
Автоматически каскадно управляет жизненным циклом и уничтожением всех дочерних элементов.
"""
def __init__(
self,
parent: ctk.CTk | ctk.CTkFrame | ctk.CTkTabview | Any,
geometry: GeometryStrategy | None = None,
) -> None:
# Потокобезопасный список для хранения дочерних компонентов Widget
self._children: List[Widget] = List()
super().__init__(parent, geometry)
[документация]
def build(self) -> ctk.CTkFrame:
"""
Строит базовый контейнерный фрейм.
Может быть переопределен в дочерних классах (например, для настройки бордеров или цвета).
"""
return ctk.CTkFrame(self.parent)
@property
def children(self) -> List[Widget]:
"""Возвращает список всех дочерних виджетов в контейнере."""
return self._children
[документация]
def add_widget[W: Widget](self, widget_cls: type[W], *args: Any, **kwargs: Any) -> W:
"""
Фабричный метод для динамического создания и добавления виджета внутрь контейнера.
Автоматически передает ctk_instance текущего контейнера в качестве родителя.
:param widget_cls: Класс создаваемого виджета (наследник Widget).
:param args: Позиционные аргументы для конструктора виджета.
:param kwargs: Именованные аргументы для конструктора виджета.
:return: Созданный экземпляр виджета.
"""
try:
# В качестве родителя для нового виджета передаем реальный CTkFrame этого контейнера
new_widget = widget_cls(self.ctk_instance, *args, **kwargs)
self._children.append(new_widget)
return new_widget
except Exception as e:
logger.error(f"Failed to add widget {widget_cls.__name__} in container {self.__class__.__name__}: {e}")
raise WidgetContainerExtensionError(
f"Failed to add widget {widget_cls.__name__} in container {self.__class__.__name__}") from e
[документация]
def remove_widget[W: Widget](self, widget: W) -> Self:
"""
Удаляет виджет из контейнера и уничтожает его ресурсы.
:param widget: Экземпляр уничтожаемого виджета.
"""
if widget in self._children:
self._children.remove(widget)
widget.destroy()
return self
[документация]
def destroy(self) -> None:
"""
Каскадно уничтожает сам контейнер и все вложенные в него дочерние виджеты,
полностью очищая оперативную память.
"""
logger.debug(f"Beginning cascade container destruction of {self.__class__.__name__}...")
# 1. Сначала уничтожаем всех детей, чтобы они очистили свои подписки в EventController
if hasattr(self, '_children') and self._children is not None:
for child in list(self._children):
try:
child.destroy()
except Exception as e:
logger.error(f"Error during destruction of child widget {child.__class__.__name__}: {e}")
self._children.clear()
self._children.clear()
# 2. Теперь вызываем базовое уничтожение самого фрейма и отписку его listeners
super().destroy()
def __del__(self) -> None:
"""
Деструктор контейнера. Гарантирует, что при удалении контейнера
не произойдет утечки памяти от забытых дочерних виджетов.
"""
self.destroy()