refactor: implement UI code optimization and refactoring

This commit implements the comprehensive UI code refactoring plan to improve
code quality, reduce duplication, and enhance maintainability.

Phase 1: High Priority Improvements
- Create BaseDialog class to eliminate window centering code duplication
  across ProgressDialog, DeleteProgressWindow, LoginDialog, and
  UserSelectionDialog (~30 lines of duplicate code removed)
- Unify log color configuration with LogTheme class in gui/log_config.py
- Create permission check decorators (@require_admin, @require_permission,
  @require_user_type, @handle_errors)
- Update all dialog classes to use unified patterns

Phase 2: Architecture Improvements
- Create input validation framework (ValidationResult, Validator,
  ValidatedWidget classes)
- Implement StateManager with observer pattern for component state sharing
- Create unified ErrorHandler for consistent error handling
- Extract CheckboxTreeview into reusable component

New Modules:
- gui/widgets/base_dialog.py - Base dialog class with modal setup and centering
- gui/utils/decorators.py - Permission and error handling decorators
- gui/utils/error_handler.py - Unified error handling with user-friendly messages
- gui/utils/state_manager.py - State management with observer pattern
- gui/utils/validators.py - Input validation framework
- gui/material_validation/checkbox_treeview.py - Reusable checkbox table

Modified Files:
- gui/log_config.py - Added LogTheme class for centralized styling
- gui/widgets/log_text.py - Use LogTheme.COLORS
- gui/widgets/progress_dialog.py - Inherit from BaseDialog
- gui/widgets/delete_progress_window.py - Inherit from BaseDialog, use LogTheme
- gui/widgets/__init__.py - Add new exports, optional imports
- gui/login_dialog.py - Use unified centering pattern
- gui/user_selection_dialog.py - Use unified centering pattern
- gui/material_validation_tab.py - Import CheckboxTreeview from new module

Benefits:
- Reduced code duplication by ~200 lines
- Improved maintainability through centralized configuration
- Better abstraction with 5 new reusable base classes
- Enhanced type safety with type hints
- Future-proof theming support via LogTheme

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka Server
2026-02-26 18:40:26 +08:00
parent 3a9c6f0978
commit f715cb97e3
16 changed files with 1735 additions and 313 deletions

21
gui/utils/__init__.py Normal file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
GUI Utilities - GUI 工具模块
提供 GUI 开发中常用的工具类和函数
"""
from gui.utils.decorators import require_admin, require_permission
from gui.utils.error_handler import ErrorHandler
from gui.utils.state_manager import StateManager
from gui.utils.validators import Validator, ValidationResult, ValidatedWidget
__all__ = [
'require_admin',
'require_permission',
'ErrorHandler',
'StateManager',
'Validator',
'ValidationResult',
'ValidatedWidget',
]

159
gui/utils/decorators.py Normal file
View File

@@ -0,0 +1,159 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Decorators - GUI 装饰器集合
提供常用的 GUI 相关装饰器,用于权限检查、异常处理等
"""
from functools import wraps
from typing import Callable, Optional
try:
from auth.session_manager import SessionManager
except ImportError:
SessionManager = None
from tkinter import messagebox
def require_admin(func: Callable) -> Callable:
"""
装饰器:要求管理员权限
如果当前用户不是管理员,显示警告消息并阻止函数执行
Usage:
@require_admin
def _open_admin_panel(self):
# 只有管理员可以执行
pass
Args:
func: 被装饰的函数
Returns:
包装后的函数
"""
@wraps(func)
def wrapper(self, *args, **kwargs):
if SessionManager is None:
# 如果没有 SessionManager直接执行向后兼容
return func(self, *args, **kwargs)
session = SessionManager.get_instance()
if not session.is_admin():
messagebox.showwarning("权限不足", "此功能需要管理员权限")
return
return func(self, *args, **kwargs)
return wrapper
def require_permission(permission: str):
"""
装饰器:要求特定权限
如果当前用户缺少指定权限,显示警告消息并阻止函数执行
Usage:
@require_permission("delete_materials")
def _delete_materials(self):
# 只有具有 delete_materials 权限的用户可以执行
pass
Args:
permission: 所需的权限名称
Returns:
装饰器函数
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(self, *args, **kwargs):
if SessionManager is None:
# 如果没有 SessionManager直接执行向后兼容
return func(self, *args, **kwargs)
session = SessionManager.get_instance()
if not session.has_permission(permission):
messagebox.showwarning("权限不足", f"缺少权限: {permission}")
return
return func(self, *args, **kwargs)
return wrapper
return decorator
def require_user_type(user_type: str):
"""
装饰器:要求特定用户类型
如果当前用户不是指定类型,显示警告消息并阻止函数执行
Usage:
@require_user_type("Admin")
def _admin_function(self):
# 只有 Admin 类型用户可以执行
pass
Args:
user_type: 所需的用户类型
Returns:
装饰器函数
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(self, *args, **kwargs):
if SessionManager is None:
# 如果没有 SessionManager直接执行向后兼容
return func(self, *args, **kwargs)
session = SessionManager.get_instance()
current_user_type = session.get_current_user_type()
if current_user_type != user_type:
messagebox.showwarning(
"权限不足",
f"此功能仅限 {user_type} 用户使用"
)
return
return func(self, *args, **kwargs)
return wrapper
return decorator
def handle_errors(
show_user: bool = True,
default_return=None,
log_context: str = ""
):
"""
装饰器:统一错误处理
捕获函数中的异常并使用 ErrorHandler 进行处理
Usage:
@handle_errors(show_user=True, log_context="Deleting materials")
def _delete_materials(self):
# 可能抛出异常的操作
pass
Args:
show_user: 是否向用户显示错误消息
default_return: 发生异常时的默认返回值
log_context: 日志上下文信息
Returns:
装饰器函数
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
from gui.utils.error_handler import ErrorHandler
context = log_context or f"{func.__name__}"
ErrorHandler.handle(e, context, show_user)
return default_return
return wrapper
return decorator

202
gui/utils/error_handler.py Normal file
View File

@@ -0,0 +1,202 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Error Handler - 统一的错误处理器
提供统一的错误处理机制,将技术错误转换为用户友好的消息
"""
import logging
import traceback
from tkinter import messagebox
from typing import Optional, Type
import sys
logger = logging.getLogger(__name__)
class ErrorHandler:
"""
统一的错误处理器
将技术错误转换为用户友好的消息,并记录详细日志
"""
# 错误类型到用户消息的映射
ERROR_MESSAGE_MAP = {
ConnectionError: "无法连接到服务器,请检查网络连接",
PermissionError: "权限不足,请联系管理员",
FileNotFoundError: "找不到指定的文件",
ValueError: "输入数据格式不正确",
TypeError: "数据类型错误,请检查输入",
KeyError: "数据缺失,请检查输入完整性",
TimeoutError: "操作超时,请重试",
RuntimeError: "运行时错误,请查看日志了解详情",
}
@staticmethod
def handle(
error: Exception,
context: str = "",
show_user: bool = True,
parent=None
):
"""
处理错误
记录详细日志,并可选择向用户显示友好的错误消息
Args:
error: 异常对象
context: 错误发生的上下文信息
show_user: 是否向用户显示错误消息
parent: 父窗口(用于显示消息框)
"""
# 记录详细日志
error_message = str(error)
if context:
logger.error(f"{context}: {error_message}", exc_info=error)
else:
logger.error(error_message, exc_info=error)
# 显示用户友好的错误信息
if show_user:
user_message = ErrorHandler._get_user_message(error)
if context:
user_message = f"[{context}]\n{user_message}"
if parent:
messagebox.showerror("操作失败", user_message, parent=parent)
else:
messagebox.showerror("操作失败", user_message)
@staticmethod
def _get_user_message(error: Exception) -> str:
"""
将技术错误转换为用户友好的消息
Args:
error: 异常对象
Returns:
用户友好的错误消息
"""
# 检查是否为已知错误类型
for error_type, message in ErrorHandler.ERROR_MESSAGE_MAP.items():
if isinstance(error, error_type):
return message
# 未知错误类型
error_name = type(error).__name__
error_msg = str(error)
# 如果错误消息为空或只包含类名,返回通用消息
if not error_msg or error_msg == error_name:
return "操作失败,请查看日志了解详情"
# 返回简化的错误消息(不包含技术细节)
# 限制长度以避免消息过长
if len(error_msg) > 200:
return f"{error_msg[:200]}..."
return error_msg
@staticmethod
def handle_with_retry(
error: Exception,
context: str = "",
retry_callback: Optional[callable] = None,
parent=None
) -> bool:
"""
处理错误并提供重试选项
Args:
error: 异常对象
context: 错误发生的上下文信息
retry_callback: 重试回调函数
parent: 父窗口
Returns:
True 如果用户选择重试False 否则
"""
user_message = ErrorHandler._get_user_message(error)
if context:
user_message = f"[{context}]\n{user_message}"
user_message += "\n\n是否重试?"
if parent:
result = messagebox.askyesno("操作失败", user_message, parent=parent)
else:
result = messagebox.askyesno("操作失败", user_message)
if result and retry_callback:
try:
retry_callback()
return True
except Exception as e:
ErrorHandler.handle(e, f"{context} (重试)", True, parent)
return False
return result
@staticmethod
def log_exception(error: Exception, context: str = ""):
"""
仅记录异常到日志,不显示用户消息
Args:
error: 异常对象
context: 错误发生的上下文信息
"""
error_message = str(error)
if context:
logger.error(f"{context}: {error_message}", exc_info=error)
else:
logger.error(error_message, exc_info=error)
@staticmethod
def show_warning(message: str, parent=None):
"""
显示警告消息
Args:
message: 警告消息
parent: 父窗口
"""
if parent:
messagebox.showwarning("警告", message, parent=parent)
else:
messagebox.showwarning("警告", message)
@staticmethod
def show_info(message: str, parent=None):
"""
显示信息消息
Args:
message: 信息消息
parent: 父窗口
"""
if parent:
messagebox.showinfo("信息", message, parent=parent)
else:
messagebox.showinfo("信息", message)
@staticmethod
def ask_confirmation(message: str, parent=None) -> bool:
"""
询问用户确认
Args:
message: 确认消息
parent: 父窗口
Returns:
True 如果用户确认False 否则
"""
if parent:
return messagebox.askyesno("确认", message, parent=parent)
else:
return messagebox.askyesno("确认", message)

241
gui/utils/state_manager.py Normal file
View File

@@ -0,0 +1,241 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
State Manager - 状态管理器
实现简单的观察者模式,用于在组件间共享状态
"""
from typing import Any, Callable, Dict, List, Set
from threading import Lock
import logging
logger = logging.getLogger(__name__)
class StateManager:
"""
简单的状态管理器
使用观察者模式,允许组件订阅状态变更通知
Usage:
# 创建全局实例
state = StateManager()
# 设置状态
state.set("current_user", "admin")
# 获取状态
user = state.get("current_user")
# 订阅状态变更
def on_user_change(new_user):
print(f"User changed to: {new_user}")
state.subscribe("current_user", on_user_change)
# 取消订阅
state.unsubscribe("current_user", on_user_change)
"""
def __init__(self):
"""初始化状态管理器"""
self._state: Dict[str, Any] = {}
self._listeners: Dict[str, List[Callable]] = {}
self._lock = Lock()
def set(self, key: str, value: Any, notify: bool = True):
"""
设置状态并通知监听器
Args:
key: 状态键
value: 状态值
notify: 是否通知监听器(默认 True
"""
with self._lock:
# 检查值是否实际变更
if key in self._state and self._state[key] == value:
return
# 更新状态
old_value = self._state.get(key)
self._state[key] = value
logger.debug(f"State changed: {key} = {value} (was: {old_value})")
# 通知监听器(在锁外部执行,避免死锁)
if notify:
self._notify(key, value, old_value)
def get(self, key: str, default: Any = None) -> Any:
"""
获取状态
Args:
key: 状态键
default: 默认值(如果键不存在)
Returns:
状态值或默认值
"""
with self._lock:
return self._state.get(key, default)
def delete(self, key: str):
"""
删除状态
Args:
key: 状态键
"""
with self._lock:
if key in self._state:
del self._state[key]
logger.debug(f"State deleted: {key}")
def subscribe(self, key: str, callback: Callable[[Any], None]):
"""
订阅状态变更
Args:
key: 状态键
callback: 回调函数,接收新值作为参数
"""
with self._lock:
if key not in self._listeners:
self._listeners[key] = []
self._listeners[key].append(callback)
logger.debug(f"New subscriber for {key}: {callback.__name__}")
def unsubscribe(self, key: str, callback: Callable[[Any], None]):
"""
取消订阅
Args:
key: 状态键
callback: 要移除的回调函数
"""
with self._lock:
if key in self._listeners:
try:
self._listeners[key].remove(callback)
logger.debug(f"Unsubscribed from {key}: {callback.__name__}")
# 如果没有监听器了,删除键
if not self._listeners[key]:
del self._listeners[key]
except ValueError:
logger.warning(f"Callback not found in subscribers for {key}")
def subscribe_all(self, callback: Callable[[str, Any, Any], None]):
"""
订阅所有状态变更
回调函数签名callback(key, new_value, old_value)
Args:
callback: 回调函数
"""
# 使用特殊的键来存储"全部"监听器
with self._lock:
special_key = "__all__"
if special_key not in self._listeners:
self._listeners[special_key] = []
self._listeners[special_key].append(callback)
logger.debug(f"New subscriber for all changes: {callback.__name__}")
def unsubscribe_all(self, callback: Callable[[str, Any, Any], None]):
"""
取消订阅所有状态变更
Args:
callback: 要移除的回调函数
"""
special_key = "__all__"
with self._lock:
if special_key in self._listeners:
try:
self._listeners[special_key].remove(callback)
logger.debug(f"Unsubscribed from all changes: {callback.__name__}")
if not self._listeners[special_key]:
del self._listeners[special_key]
except ValueError:
logger.warning(f"Callback not found in all subscribers")
def _notify(self, key: str, new_value: Any, old_value: Any):
"""
通知所有订阅者
Args:
key: 状态键
new_value: 新值
old_value: 旧值
"""
with self._lock:
# 获取该键的监听器
listeners = self._listeners.get(key, []).copy()
# 获取"全部"监听器
all_listeners = self._listeners.get("__all__", []).copy()
# 在锁外部调用回调,避免死锁
for callback in listeners:
try:
callback(new_value)
except Exception as e:
logger.error(f"Error in state change listener for {key}: {e}", exc_info=True)
for callback in all_listeners:
try:
callback(key, new_value, old_value)
except Exception as e:
logger.error(f"Error in all-state listener for {key}: {e}", exc_info=True)
def get_all(self) -> Dict[str, Any]:
"""
获取所有状态的副本
Returns:
包含所有状态的字典
"""
with self._lock:
return self._state.copy()
def clear(self):
"""清空所有状态"""
with self._lock:
self._state.clear()
self._listeners.clear()
logger.debug("All state cleared")
def has_key(self, key: str) -> bool:
"""
检查是否存在指定键
Args:
key: 状态键
Returns:
True 如果键存在False 否则
"""
with self._lock:
return key in self._state
# 全局状态管理器实例
_global_state_manager: StateManager = None
def get_global_state_manager() -> StateManager:
"""
获取全局状态管理器实例(单例模式)
Returns:
全局 StateManager 实例
"""
global _global_state_manager
if _global_state_manager is None:
_global_state_manager = StateManager()
return _global_state_manager

431
gui/utils/validators.py Normal file
View File

@@ -0,0 +1,431 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Validators - 输入验证框架
提供统一的输入验证机制,用于 GUI 表单验证
"""
import os
import re
from datetime import datetime
from typing import Any, Callable, List, Optional
from tkinter import ttk
class ValidationResult:
"""
验证结果
表示验证操作的结果,包含是否成功和错误消息
"""
def __init__(self, is_valid: bool, error_message: str = ""):
"""
初始化验证结果
Args:
is_valid: 是否验证通过
error_message: 错误消息(验证失败时)
"""
self.is_valid = is_valid
self.error_message = error_message
def __bool__(self) -> bool:
"""允许直接在 if 语句中使用"""
return self.is_valid
def __str__(self) -> str:
"""返回错误消息或"验证通过" """
return self.error_message if not self.is_valid else "验证通过"
class Validator:
"""
输入验证器基类
提供常用的静态验证方法
"""
@staticmethod
def not_empty(value: Any, field_name: str = "字段") -> ValidationResult:
"""
非空验证
Args:
value: 要验证的值
field_name: 字段名称(用于错误消息)
Returns:
验证结果
"""
if value is None or value == "":
return ValidationResult(False, f"{field_name}不能为空")
if isinstance(value, str) and not value.strip():
return ValidationResult(False, f"{field_name}不能为空或仅包含空格")
return ValidationResult(True)
@staticmethod
def file_exists(path: str, field_name: str = "文件") -> ValidationResult:
"""
文件存在性验证
Args:
path: 文件路径
field_name: 字段名称
Returns:
验证结果
"""
if not path:
return ValidationResult(False, f"{field_name}路径不能为空")
if not os.path.exists(path):
return ValidationResult(False, f"{field_name}不存在: {path}")
return ValidationResult(True)
@staticmethod
def dir_exists(path: str, field_name: str = "目录") -> ValidationResult:
"""
目录存在性验证
Args:
path: 目录路径
field_name: 字段名称
Returns:
验证结果
"""
if not path:
return ValidationResult(False, f"{field_name}路径不能为空")
if not os.path.exists(path):
return ValidationResult(False, f"{field_name}不存在: {path}")
if not os.path.isdir(path):
return ValidationResult(False, f"{field_name}不是有效的目录: {path}")
return ValidationResult(True)
@staticmethod
def date_format(value: str, format_str: str = "%Y-%m-%d", field_name: str = "日期") -> ValidationResult:
"""
日期格式验证
Args:
value: 日期字符串
format_str: 期望的日期格式
field_name: 字段名称
Returns:
验证结果
"""
if not value:
return ValidationResult(False, f"{field_name}不能为空")
try:
datetime.strptime(value, format_str)
return ValidationResult(True)
except ValueError:
return ValidationResult(False, f"{field_name}格式错误,期望格式: {format_str}")
@staticmethod
def numeric(value: Any, field_name: str = "数值", min_value: Optional[float] = None,
max_value: Optional[float] = None) -> ValidationResult:
"""
数值验证
Args:
value: 要验证的值
field_name: 字段名称
min_value: 最小值(可选)
max_value: 最大值(可选)
Returns:
验证结果
"""
if value is None or value == "":
return ValidationResult(False, f"{field_name}不能为空")
try:
num = float(value)
except (ValueError, TypeError):
return ValidationResult(False, f"{field_name}必须是有效的数字")
if min_value is not None and num < min_value:
return ValidationResult(False, f"{field_name}不能小于 {min_value}")
if max_value is not None and num > max_value:
return ValidationResult(False, f"{field_name}不能大于 {max_value}")
return ValidationResult(True)
@staticmethod
def integer(value: Any, field_name: str = "整数") -> ValidationResult:
"""
整数验证
Args:
value: 要验证的值
field_name: 字段名称
Returns:
验证结果
"""
if value is None or value == "":
return ValidationResult(False, f"{field_name}不能为空")
try:
int(value)
return ValidationResult(True)
except (ValueError, TypeError):
return ValidationResult(False, f"{field_name}必须是有效的整数")
@staticmethod
def length(value: str, min_length: int = 0, max_length: Optional[int] = None,
field_name: str = "字段") -> ValidationResult:
"""
字符串长度验证
Args:
value: 要验证的字符串
min_length: 最小长度
max_length: 最大长度(可选)
field_name: 字段名称
Returns:
验证结果
"""
if not isinstance(value, str):
return ValidationResult(False, f"{field_name}必须是字符串")
length = len(value)
if length < min_length:
return ValidationResult(False, f"{field_name}长度不能少于 {min_length} 个字符")
if max_length is not None and length > max_length:
return ValidationResult(False, f"{field_name}长度不能超过 {max_length} 个字符")
return ValidationResult(True)
@staticmethod
def regex(value: str, pattern: str, field_name: str = "字段") -> ValidationResult:
"""
正则表达式验证
Args:
value: 要验证的字符串
pattern: 正则表达式模式
field_name: 字段名称
Returns:
验证结果
"""
if not isinstance(value, str):
return ValidationResult(False, f"{field_name}必须是字符串")
if not re.match(pattern, value):
return ValidationResult(False, f"{field_name}格式不正确")
return ValidationResult(True)
@staticmethod
def email(value: str, field_name: str = "邮箱") -> ValidationResult:
"""
邮箱格式验证
Args:
value: 邮箱地址
field_name: 字段名称
Returns:
验证结果
"""
if not value:
return ValidationResult(False, f"{field_name}不能为空")
# 简单的邮箱正则表达式
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return Validator.regex(value, pattern, field_name)
@staticmethod
def phone(value: str, field_name: str = "手机号") -> ValidationResult:
"""
手机号验证(中国大陆)
Args:
value: 手机号
field_name: 字段名称
Returns:
验证结果
"""
if not value:
return ValidationResult(False, f"{field_name}不能为空")
# 中国大陆手机号正则表达式
pattern = r'^1[3-9]\d{9}$'
return Validator.regex(value, pattern, field_name)
@staticmethod
def in_range(value: Any, allowed_values: List[Any], field_name: str = "字段") -> ValidationResult:
"""
值范围验证
Args:
value: 要验证的值
allowed_values: 允许的值列表
field_name: 字段名称
Returns:
验证结果
"""
if value not in allowed_values:
return ValidationResult(
False,
f"{field_name}必须是以下值之一: {', '.join(str(v) for v in allowed_values)}"
)
return ValidationResult(True)
@staticmethod
def custom(value: Any, validator_func: Callable[[Any], bool],
error_message: str = "验证失败") -> ValidationResult:
"""
自定义验证函数
Args:
value: 要验证的值
validator_func: 验证函数,返回 True 表示验证通过
error_message: 错误消息
Returns:
验证结果
"""
try:
if validator_func(value):
return ValidationResult(True)
return ValidationResult(False, error_message)
except Exception as e:
return ValidationResult(False, f"验证过程出错: {str(e)}")
class ValidatedWidget:
"""
带验证功能的 Widget 基类
为任何支持 get_value() 方法的组件添加验证功能
"""
def __init__(self, widget: ttk.Widget, error_label: Optional[ttk.Label] = None):
"""
初始化验证组件
Args:
widget: 要验证的组件(必须支持 get_value() 方法)
error_label: 用于显示错误消息的 Label可选
"""
self.widget = widget
self.error_label = error_label
self.validators: List[Callable[[Any], ValidationResult]] = []
self._last_result: Optional[ValidationResult] = None
def add_validator(self, validator: Callable[[Any], ValidationResult]):
"""
添加验证器
Args:
validator: 验证函数,接收值并返回 ValidationResult
"""
self.validators.append(validator)
def add_not_empty_validator(self, field_name: str = "字段"):
"""
添加非空验证器
Args:
field_name: 字段名称
"""
self.add_validator(lambda v: Validator.not_empty(v, field_name))
def add_file_exists_validator(self, field_name: str = "文件"):
"""
添加文件存在性验证器
Args:
field_name: 字段名称
"""
self.add_validator(lambda v: Validator.file_exists(v, field_name))
def add_date_format_validator(self, format_str: str = "%Y-%m-%d", field_name: str = "日期"):
"""
添加日期格式验证器
Args:
format_str: 日期格式
field_name: 字段名称
"""
self.add_validator(lambda v: Validator.date_format(v, format_str, field_name))
def add_custom_validator(self, validator_func: Callable[[Any], bool], error_message: str = "验证失败"):
"""
添加自定义验证器
Args:
validator_func: 验证函数
error_message: 错误消息
"""
self.add_validator(lambda v: Validator.custom(v, validator_func, error_message))
def validate(self) -> bool:
"""
执行所有验证
Returns:
True 如果所有验证都通过False 否则
"""
value = self._get_value()
for validator in self.validators:
result = validator(value)
self._last_result = result
if not result.is_valid:
self._show_error(result.error_message)
return False
self._clear_error()
return True
def _get_value(self) -> Any:
"""
获取组件的值
Returns:
组件的当前值
"""
if hasattr(self.widget, 'get'):
return self.widget.get()
elif hasattr(self.widget, 'cget'):
# 对于某些组件,尝试获取配置值
return self.widget.cget('text')
else:
raise AttributeError(f"Widget {type(self.widget).__name__} 不支持获取值")
def _show_error(self, message: str):
"""
显示错误消息
Args:
message: 错误消息
"""
if self.error_label:
self.error_label.config(text=message, foreground="red")
# 也可以添加其他错误显示方式,比如改变组件边框颜色
def _clear_error(self):
"""清除错误消息"""
if self.error_label:
self.error_label.config(text="")
def get_last_result(self) -> Optional[ValidationResult]:
"""
获取最后一次验证结果
Returns:
最后一次验证的 ValidationResult
"""
return self._last_result