Исходный код EJIO.ctk_extender.widget.form.fields.option_menu
## /ctk_extender/widget/form/fields/option_menu_field.py
import customtkinter as ctk
from typing import Any, Literal
from .base import InputField
from ...container import WidgetContainer
from ...components import Label
from ...geometry import GeometryStrategy, GridStrategy
__all__ = [
'FormOptionMenuField',
]
FieldLayout = Literal['row', 'column']
[документация]
class FormOptionMenuField(WidgetContainer, InputField):
"""
Высокоуровневое поле формы на базе выпадающего списка ctk.CTkOptionMenu.
Интегрировано в подсистему валидации форм.
"""
__slots__ = ('_name', '_label_text', '_values', '_default_value', '_layout', '_required', '_menu_instance')
def __init__(
self,
parent: Any,
name: str,
label_text: str,
values: list[str],
default_value: str | None = None,
layout: FieldLayout = 'row',
required: bool = False,
geometry: GeometryStrategy | None = None,
**kwargs: Any
) -> None:
self._name: str = name
self._label_text: str = label_text
self._values: list[str] = values
# Если дефолтное значение не задано, берем первый элемент списка
self._default_value: str = default_value or (values[0] if values else "")
self._layout: FieldLayout = layout
self._required: bool = required
self._menu_instance: ctk.CTkOptionMenu | None = None
super().__init__(parent, geometry, **kwargs)
[документация]
def build(self) -> ctk.CTkFrame:
"""Строит каркас поля формы с поддержкой сеточного позиционирования."""
frame_instance = super().build()
frame_instance.configure(fg_color="transparent")
# 1. Создаем текстовую подпись (Label)
lbl = self.add_widget(Label, text=self._label_text)
# 2. Создаем нативный выпадающий список ctk.CTkOptionMenu
# Значение переменной ctk.StringVar будет отслеживать выбранный элемент
self._menu_instance = ctk.CTkOptionMenu(
frame_instance,
values=self._values,
**self._kwargs
)
self._menu_instance.set(self._default_value)
# 3. Выстраиваем сетку grid в зависимости от режима layout
if self._layout == 'row':
lbl.set_geometry(GridStrategy(row=0, column=0, sticky="w", padx=(0, 10), pady=2))
self._menu_instance.grid(row=0, column=1, sticky="ew", pady=2)
frame_instance.grid_columnconfigure(1, weight=1)
else:
lbl.set_geometry(GridStrategy(row=0, column=0, sticky="w", pady=(0, 2)))
self._menu_instance.grid(row=1, column=0, sticky="ew", pady=(0, 2))
frame_instance.grid_columnconfigure(0, weight=1)
return frame_instance
@property
def name(self) -> str:
return self._name
[документация]
def get_value(self) -> str:
if self._menu_instance is None:
return ""
return str(self._menu_instance.get())
[документация]
def set_value(self, value: Any) -> None:
if self._menu_instance is not None:
self._menu_instance.set(str(value))
[документация]
def clear(self) -> None:
if self._menu_instance is not None:
self._menu_instance.set(self._default_value)
[документация]
def validate(self) -> str | None:
"""Проверяет, выбрано ли значение, если поле обязательно."""
current_val = self.get_value()
if self._required and (not current_val or current_val == self._default_value and len(
self._values) > 1 and self._default_value == self._values[0]):
# Кастомная логика: если поле обязательно, пользователь должен выбрать элемент, отличный от заглушки
return f"Пожалуйста, выберите значение в поле '{self._label_text}'."
return None