fix: 全局UX修复与Step4交互可视化重构
=== 自动填入过于激进(幽灵路径级联)=== - 所有面板 update_from_config 移除 os.makedirs(),目录创建留给 pipeline 执行 - 输出路径仅 widget 为空时填入默认值,不覆盖用户已选 - 输入路径从上游读取后添加 os.path.exists() 检查,阻断幽灵路径级联 - panel_factory._replay_live_panel_inputs 广播前校验文件确实存在 - step10 update_from_config 添加 os.path.isfile() 存在性检查 - 清理 step8/9/11 中冗余局部 import os(修复 UnboundLocalError) === 输出目录缺失 === - step7 新增 output_file FileSelectWidget,默认路径 7_Water_Quality_Indices/ - step9 output_file 从文件模式改为目录模式 (Directories) === 空目录自动创建 === - step12 _setup_prediction_output_dirs 移除 mkdir() 调用,改为只读日志 === 过期依赖与缺失 import === - panel_registry Step10 依赖 bsq_file→sampling_csv_file(匹配 CSV 模式重构) - step7 添加缺失的 import pandas as pd(修复 NameError) === 导航与 UI 一致性 === - water_quality_gui_v2 新增 _select_first_nav_item(),启动时默认选中第一项 - step1 输出卡片对齐 step8 风格 - step8 补充缺失的样式表和统一边距 === Step4 交互式光谱探针重构 === - 左右分栏 QSplitter 布局:左侧控制区 + 右侧 Matplotlib 视图 - 1x2 子图:ax1 散点图 + ax2 光谱曲线 - Hover 悬停 Annotation 显示坐标,Click 点击高亮+绘制光谱 - NavigationToolbar2QT 工具栏(保存/缩放/平移) - 自动检测坐标列和波段列,完善异常处理
This commit is contained in:
@ -3,12 +3,17 @@
|
||||
"""
|
||||
轻量级事件总线
|
||||
|
||||
支持 subscribe(event_name, callback) 和 publish(event_name, data),
|
||||
用于步骤面板间的去中心化参数传导。
|
||||
支持 subscribe(event_name, callback)、unsubscribe(event_name, callback)
|
||||
和 publish(event_name, data),用于步骤面板间的去中心化参数传导。
|
||||
|
||||
2026-06-30 修复:
|
||||
- publish() 中的 except Exception: pass 改为 traceback 日志输出(静默吞异常会导致 bug 无法发现)
|
||||
- 新增 unsubscribe() 方法,允许面板销毁时清理订阅,防止内存泄漏和野指针回调
|
||||
"""
|
||||
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from typing import Any, Callable, Dict, List
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
|
||||
class EventBus:
|
||||
@ -16,18 +21,48 @@ class EventBus:
|
||||
|
||||
def __init__(self):
|
||||
self._subscribers: Dict[str, List[Callable]] = defaultdict(list)
|
||||
# 异常日志回调(可选注入,供 LogManager 使用)
|
||||
self._error_logger: Optional[Callable[[str], None]] = None
|
||||
|
||||
def set_error_logger(self, logger: Callable[[str], None]):
|
||||
"""注入异常日志回调。若不设置,异常信息输出到 stderr。"""
|
||||
self._error_logger = logger
|
||||
|
||||
def subscribe(self, event_name: str, callback: Callable[[dict], None]):
|
||||
"""订阅事件。callback 接收一个 dict 作为事件数据。"""
|
||||
self._subscribers[event_name].append(callback)
|
||||
if callback not in self._subscribers[event_name]:
|
||||
self._subscribers[event_name].append(callback)
|
||||
|
||||
def unsubscribe(self, event_name: str, callback: Callable[[dict], None]):
|
||||
"""取消订阅。面板销毁或不再需要接收事件时调用,防止野指针回调。"""
|
||||
subs = self._subscribers.get(event_name)
|
||||
if subs and callback in subs:
|
||||
subs.remove(callback)
|
||||
# 清理空列表,避免字典膨胀
|
||||
if not subs:
|
||||
del self._subscribers[event_name]
|
||||
|
||||
def publish(self, event_name: str, data: Dict[str, Any]):
|
||||
"""发布事件,通知所有订阅者。"""
|
||||
for callback in self._subscribers.get(event_name, []):
|
||||
"""发布事件,通知所有订阅者。订阅者异常不再静默吞掉,而是输出 traceback。
|
||||
|
||||
迭代订阅者列表的副本,防止回调中调用 unsubscribe() 导致跳过后续订阅者。
|
||||
"""
|
||||
for callback in list(self._subscribers.get(event_name, [])):
|
||||
try:
|
||||
callback(data)
|
||||
except Exception:
|
||||
pass
|
||||
err_msg = (
|
||||
f"[EventBus] 事件 '{event_name}' 的订阅者 {callback.__name__!r} 抛出异常:\n"
|
||||
+ traceback.format_exc()
|
||||
)
|
||||
if self._error_logger:
|
||||
try:
|
||||
self._error_logger(err_msg)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
import sys
|
||||
print(err_msg, file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
# 全局单例
|
||||
|
||||
Reference in New Issue
Block a user