From 92cba114a93489b8320e6c41b8a2474d36c91ed6 Mon Sep 17 00:00:00 2001 From: "xinzhu.yin" Date: Mon, 22 Jun 2026 10:40:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96UI=E5=8A=A0=E8=BD=BD=E9=80=9F?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/panel_manager.py | 204 +++++++++++++++------ app/views/panels/ai_image_panel.py | 6 +- app/views/panels/calman_panel.py | 40 +++- app/views/panels/gamma_pattern_panel.py | 2 +- app/views/panels/main_layout.py | 48 +++-- app/views/panels/pantone_baseline_panel.py | 2 +- app/views/panels/side_panels.py | 4 +- pqAutomationApp.py | 39 ++-- settings/pq_config.json | 2 +- 9 files changed, 239 insertions(+), 108 deletions(-) diff --git a/app/views/panel_manager.py b/app/views/panel_manager.py index e424459..e45a17c 100644 --- a/app/views/panel_manager.py +++ b/app/views/panel_manager.py @@ -13,6 +13,11 @@ from typing import TYPE_CHECKING, Any, Callable if TYPE_CHECKING: from pqAutomationApp import PQAutomationApp +# 叠层切换:lift/lower,避免 pack_forget 触发 Matplotlib 等大面板重绘。 +_STACK_PANELS = frozenset( + {"log", "ai_image", "pantone_baseline", "gamma_pattern", "calman", "single_step"} +) + def create_tool_panel_sidebar_item( self: "PQAutomationApp", parent: tk.Misc, label: str, command: Callable[[], None] @@ -175,69 +180,42 @@ def register_panel(self: "PQAutomationApp", panel_name, frame, button, visible_a } -def show_panel(self: "PQAutomationApp", panel_name): - """显示指定面板,隐藏其他所有面板""" - if panel_name not in self.panels: +def setup_view_stack(self: "PQAutomationApp") -> None: + """将所有工具面板叠放在 view_stack 上,切换时仅调整 Z 序。""" + if getattr(self, "_view_stack_ready", False): return - # 如果当前面板就是要显示的面板,则隐藏它 - if self.current_panel == panel_name: - self.hide_all_panels() - # 如果当前测试类型是 Local Dimming,则在关闭日志等面板后自动恢复 Local Dimming 面板 - try: - if ( - getattr(self, "config", None) - and getattr(self.config, "current_test_type", None) == "local_dimming" - and panel_name != "local_dimming" - ): - self.show_panel("local_dimming") - except Exception: - pass - return - - # 隐藏所有面板 - self.hide_all_panels() - - # 显示指定面板 - panel_info = self.panels[panel_name] - - # 隐藏主内容区域。 - # Local Dimming 作为并列测试类型时,需要保留顶部配置区, - # 让用户在面板上方直接看到并修改配置项。 - if panel_name == "local_dimming": - # 重新按“自适应高度”布局顶部配置区,避免其占用可扩展空间把 - # Local Dimming 主面板整体向下挤出大块空白。 - self.control_frame_top.pack_forget() - self.control_frame_top.pack( - side=tk.TOP, fill=tk.X, expand=False, padx=0, pady=5 - ) - self.control_frame_middle.pack_forget() - self.control_frame_bottom.pack_forget() - else: - self.control_frame_top.pack_forget() - self.control_frame_middle.pack_forget() - self.control_frame_bottom.pack_forget() - - # 显示目标面板 - panel_info["frame"].pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5) - - # 更新侧栏选中样式 - _set_tool_panel_sidebar_selected(panel_info, True) - - # 更新状态 - setattr(self, panel_info["visible_attr"], True) - self.current_panel = panel_name - - -def hide_all_panels(self: "PQAutomationApp"): - """隐藏所有面板,显示主内容区域""" - # 隐藏所有注册的面板 + self.main_view.place(relx=0, rely=0, relwidth=1, relheight=1) for panel_name, panel_info in self.panels.items(): - panel_info["frame"].pack_forget() + if panel_name == "local_dimming": + continue + panel_info["frame"].place(relx=0, rely=0, relwidth=1, relheight=1) + panel_info["frame"].lower() + + self._view_stack_ready = True + + +def _lower_stack_panels(self: "PQAutomationApp") -> None: + for panel_name, panel_info in self.panels.items(): + if panel_name in _STACK_PANELS: + panel_info["frame"].lower() + + +def _clear_panel_selection(self: "PQAutomationApp") -> None: + for panel_info in self.panels.values(): _set_tool_panel_sidebar_selected(panel_info, False) setattr(self, panel_info["visible_attr"], False) - # 显示主内容区域 + +def _restore_main_view_layout(self: "PQAutomationApp") -> None: + """恢复主界面三区布局,并收起嵌入的 Local Dimming 面板。""" + if getattr(self, "_ld_embedded", False): + self.local_dimming_frame.pack_forget() + self._ld_embedded = False + + self.control_frame_top.pack_forget() + self.control_frame_middle.pack_forget() + self.control_frame_bottom.pack_forget() self.control_frame_top.pack( side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 ) @@ -248,6 +226,14 @@ def hide_all_panels(self: "PQAutomationApp"): side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 ) + +def _show_main_content(self: "PQAutomationApp") -> None: + """显示主内容区并清除当前面板状态。""" + setup_view_stack(self) + _lower_stack_panels(self) + _restore_main_view_layout(self) + self.main_view.lift() + _clear_panel_selection(self) self.current_panel = None if hasattr(self, "_sync_save_button_state"): @@ -257,6 +243,109 @@ def hide_all_panels(self: "PQAutomationApp"): pass +def _notify_panel_shown(self: "PQAutomationApp", panel_name: str) -> None: + """面板显示后的轻量刷新(延迟执行,不阻塞切换)。""" + if panel_name == "calman": + refresh = getattr(self, "_on_calman_panel_shown", None) + if refresh is not None: + self.root.after_idle(refresh) + + +def _show_stack_panel(self: "PQAutomationApp", panel_name: str) -> None: + """叠层模式显示工具面板。""" + setup_view_stack(self) + panel_info = self.panels[panel_name] + + _set_tool_panel_sidebar_selected(panel_info, True) + + previous = self.current_panel + if previous == "local_dimming": + self.local_dimming_frame.pack_forget() + self._ld_embedded = False + elif previous is not None and previous in _STACK_PANELS: + prev_info = self.panels[previous] + _set_tool_panel_sidebar_selected(prev_info, False) + setattr(self, prev_info["visible_attr"], False) + prev_info["frame"].lower() + + self.main_view.lower() + panel_info["frame"].lift() + setattr(self, panel_info["visible_attr"], True) + self.current_panel = panel_name + _notify_panel_shown(self, panel_name) + + +def _show_local_dimming_panel(self: "PQAutomationApp") -> None: + """Local Dimming:保留顶部配置区,下方嵌入 LD 面板。""" + setup_view_stack(self) + panel_info = self.panels["local_dimming"] + + if self.current_panel in _STACK_PANELS: + prev_info = self.panels[self.current_panel] + _set_tool_panel_sidebar_selected(prev_info, False) + setattr(self, prev_info["visible_attr"], False) + prev_info["frame"].lower() + + _set_tool_panel_sidebar_selected(panel_info, True) + _lower_stack_panels(self) + self.main_view.lift() + + self.control_frame_top.pack_forget() + self.control_frame_top.pack( + side=tk.TOP, fill=tk.X, expand=False, padx=0, pady=5 + ) + self.control_frame_middle.pack_forget() + self.control_frame_bottom.pack_forget() + self.local_dimming_frame.pack( + side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5 + ) + self._ld_embedded = True + + setattr(self, panel_info["visible_attr"], True) + self.current_panel = "local_dimming" + + +def _maybe_restore_local_dimming_panel( + self: "PQAutomationApp", closed_panel: str +) -> None: + """关闭工具面板后,Local Dimming 测试类型下自动恢复 LD 面板。""" + if closed_panel == "local_dimming": + return + try: + if ( + getattr(self, "config", None) + and getattr(self.config, "current_test_type", None) == "local_dimming" + ): + self.show_panel("local_dimming") + except Exception: + pass + + +def show_panel(self: "PQAutomationApp", panel_name): + """显示指定面板,隐藏其他所有面板""" + if panel_name not in self.panels: + return + + if self.current_panel == panel_name: + _show_main_content(self) + _maybe_restore_local_dimming_panel(self, panel_name) + return + + if panel_name == "local_dimming": + _show_local_dimming_panel(self) + return + + if panel_name in _STACK_PANELS: + _show_stack_panel(self, panel_name) + return + + # 未知面板类型:回退到叠层显示 + _show_stack_panel(self, panel_name) + + +def hide_all_panels(self: "PQAutomationApp"): + """隐藏所有面板,显示主内容区域""" + _show_main_content(self) class PanelManagerMixin: @@ -265,6 +354,7 @@ class PanelManagerMixin: """ create_tool_panel_sidebar_item = create_tool_panel_sidebar_item register_panel = register_panel + setup_view_stack = setup_view_stack show_panel = show_panel hide_all_panels = hide_all_panels refresh_tool_panel_sidebar_theme = refresh_tool_panel_sidebar_theme diff --git a/app/views/panels/ai_image_panel.py b/app/views/panels/ai_image_panel.py index 44aee8d..c94a032 100644 --- a/app/views/panels/ai_image_panel.py +++ b/app/views/panels/ai_image_panel.py @@ -163,7 +163,7 @@ def _on_tree_motion(self: "PQAutomationApp", event): def create_ai_image_panel(self: "PQAutomationApp"): """创建 AI 图片对话面板,并注册到面板管理。""" - frame = ttk.Frame(self.content_frame) + frame = ttk.Frame(self.view_stack) self.ai_image_frame = frame # 内部状态 @@ -413,9 +413,9 @@ def create_ai_image_panel(self: "PQAutomationApp"): def toggle_ai_image_panel(self: "PQAutomationApp"): """切换 AI 图片面板显隐。""" self.show_panel("ai_image") - _apply_ai_image_list_style(self) + self.root.after_idle(lambda: _apply_ai_image_list_style(self)) if not getattr(self, "_ai_image_list_loaded", False): - _start_new_session(self) + self.root.after_idle(lambda: _start_new_session(self)) def _get_app_base_dir(self: "PQAutomationApp") -> str: diff --git a/app/views/panels/calman_panel.py b/app/views/panels/calman_panel.py index 27d193d..d1d106c 100644 --- a/app/views/panels/calman_panel.py +++ b/app/views/panels/calman_panel.py @@ -317,7 +317,7 @@ def _refresh_calman_config_summary(self: "PQAutomationApp") -> None: def create_calman_panel(self: "PQAutomationApp") -> None: """创建 CALMAN 风格灰阶测试面板,注册到 panel_manager。""" palette = _get_calman_palette() - self.calman_frame = ttk.Frame(self.content_frame) + self.calman_frame = ttk.Frame(self.view_stack) self.calman_visible = False self.calman_levels = list(DEFAULT_LEVELS_PCT) # level_pct -> dict(pct, x, y, Y, X, Z, cct, gamma, de2000, rgb_r, rgb_g, rgb_b, time) @@ -677,22 +677,45 @@ def create_calman_panel(self: "PQAutomationApp") -> None: _apply_calman_tree_style(self) - right.bind("", lambda _e: _adaptive_matrix_columns(self)) + right.bind("", lambda _e: _schedule_adaptive_matrix_columns(self)) - _refresh_metric_table(self) - _refresh_calman_config_summary(self) - _update_target_strip(self) - _update_actual_strip(self) - _redraw_calman_charts(self) + self.root.after_idle(lambda: _finish_calman_panel_init(self)) # 注册到统一面板管理(按钮稍后由 main_layout 注入) self.register_panel("calman", self.calman_frame, None, "calman_visible") +def _finish_calman_panel_init(self: "PQAutomationApp") -> None: + """延迟初始化 CALMAN 图表与表格,避免阻塞主窗口首屏。""" + _refresh_metric_table(self) + _refresh_calman_config_summary(self) + _update_target_strip(self) + _update_actual_strip(self) + _redraw_calman_charts(self) + self._calman_panel_initialized = True + + +def _on_calman_panel_shown(self: "PQAutomationApp") -> None: + """面板切到前台时只做轻量刷新,不重绘 Matplotlib。""" + _refresh_calman_config_summary(self) + _schedule_adaptive_matrix_columns(self) + + +def _schedule_adaptive_matrix_columns(self: "PQAutomationApp") -> None: + job = getattr(self, "_calman_matrix_col_job", None) + if job is not None: + try: + self.root.after_cancel(job) + except Exception: + pass + self._calman_matrix_col_job = self.root.after_idle( + lambda: _adaptive_matrix_columns(self) + ) + + def toggle_calman_panel(self: "PQAutomationApp") -> None: """切换 CALMAN 灰阶面板显示。""" self.show_panel("calman") - _refresh_calman_config_summary(self) # --------------------------------------------------------------------------- @@ -1374,3 +1397,4 @@ class CalmanPanelMixin: create_calman_panel = create_calman_panel toggle_calman_panel = toggle_calman_panel refresh_calman_theme = refresh_calman_theme + _on_calman_panel_shown = _on_calman_panel_shown diff --git a/app/views/panels/gamma_pattern_panel.py b/app/views/panels/gamma_pattern_panel.py index bb67f78..72e1311 100644 --- a/app/views/panels/gamma_pattern_panel.py +++ b/app/views/panels/gamma_pattern_panel.py @@ -148,7 +148,7 @@ _GENERATORS = { def create_gamma_pattern_panel(self: "PQAutomationApp"): """创建灰阶 Pattern 配置面板。""" - frame = ttk.Frame(self.content_frame) + frame = ttk.Frame(self.view_stack) self.gamma_pattern_frame = frame self.gamma_pattern_visible = False diff --git a/app/views/panels/main_layout.py b/app/views/panels/main_layout.py index b9148a0..88fe52a 100644 --- a/app/views/panels/main_layout.py +++ b/app/views/panels/main_layout.py @@ -171,8 +171,32 @@ def create_test_items_content(self: "PQAutomationApp"): }, } - # 根据当前测试类型创建复选框 + # 预创建各测试类型的复选框,切换时只改显隐与勾选状态,避免重复构建控件。 self.test_vars = {} + toggle_bootstyle = "success-round-toggle" + for type_key, config in self.test_items.items(): + frame = config["frame"] + type_label = ttk.Label( + frame, + text=self.get_test_type_name(type_key), + style="primary.TLabel", + ) + type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3) + config["type_label"] = type_label + + checkbox_vars: dict[str, tk.BooleanVar] = {} + for i, (text, var_name) in enumerate(config["items"]): + var = tk.BooleanVar(value=False) + ttk.Checkbutton( + frame, + text=text, + variable=var, + bootstyle=toggle_bootstyle, + command=self.update_config_and_tabs, + ).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5) + checkbox_vars[var_name] = var + config["checkbox_vars"] = checkbox_vars + self.update_test_items() # 创建色度参数设置框架 @@ -1243,33 +1267,17 @@ def update_test_items(self: "PQAutomationApp"): frame = config["frame"] frame.pack(fill=tk.X, padx=5, pady=5) - # 添加测试类型标签 - type_label = ttk.Label( - frame, + config["type_label"].configure( text=self.get_test_type_name(current_test_type), - style="primary.TLabel", ) - type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3) - # 从配置中读取保存的选择状态 saved_test_items = self.config.current_test_types[current_test_type].get( "test_items", [] ) - # 添加复选框 - toggle_bootstyle = "success-round-toggle" - for i, (text, var_name) in enumerate(config["items"]): - is_checked = var_name in saved_test_items - var = tk.BooleanVar(value=is_checked) - + for var_name, var in config["checkbox_vars"].items(): + var.set(var_name in saved_test_items) self.test_vars[f"{current_test_type}_{var_name}"] = var - ttk.Checkbutton( - frame, - text=text, - variable=var, - bootstyle=toggle_bootstyle, - command=self.update_config_and_tabs, - ).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5) if hasattr(self, "chart_notebook"): self.update_chart_tabs_state() diff --git a/app/views/panels/pantone_baseline_panel.py b/app/views/panels/pantone_baseline_panel.py index ae5c8e5..a58bdc9 100644 --- a/app/views/panels/pantone_baseline_panel.py +++ b/app/views/panels/pantone_baseline_panel.py @@ -23,7 +23,7 @@ _TEMPLATE_FILE = "pantone_2670_colors.xlsx" def create_pantone_baseline_panel(self: "PQAutomationApp"): """创建 Pantone 认证摸底测试面板。""" - frame = ttk.Frame(self.content_frame) + frame = ttk.Frame(self.view_stack) self.pantone_baseline_frame = frame self.pantone_baseline_visible = False self.pantone_patterns = [] diff --git a/app/views/panels/side_panels.py b/app/views/panels/side_panels.py index 4409004..deab72b 100644 --- a/app/views/panels/side_panels.py +++ b/app/views/panels/side_panels.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: def create_log_panel(self: "PQAutomationApp"): """创建日志面板""" - self.log_frame = ttk.Frame(self.content_frame) + self.log_frame = ttk.Frame(self.view_stack) self.log_gui = PQLogGUI(self.log_frame) self.log_gui.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) @@ -30,7 +30,7 @@ def create_log_panel(self: "PQAutomationApp"): def create_local_dimming_panel(self: "PQAutomationApp"): """创建 Local Dimming 测试面板。""" - self.local_dimming_frame = ttk.Frame(self.content_frame) + self.local_dimming_frame = ttk.Frame(self.main_view) # 主容器 main_container = ttk.Frame(self.local_dimming_frame, padding=10) diff --git a/pqAutomationApp.py b/pqAutomationApp.py index 5c3fce3..525e20b 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -173,18 +173,25 @@ class PQAutomationApp( side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5 ) + # 叠层容器:主界面与工具面板同尺寸叠放,切换时用 lift 而非 pack_forget。 + self.view_stack = ttk.Frame(self.content_frame) + self.view_stack.pack(fill=tk.BOTH, expand=True) + self.main_view = ttk.Frame(self.view_stack) + self._ld_embedded = False + self._view_stack_ready = False + # 创建右侧内容区域的上中下三个分区 - self.control_frame_top = ttk.Frame(self.content_frame) + self.control_frame_top = ttk.Frame(self.main_view) self.control_frame_top.pack( side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 ) - self.control_frame_middle = ttk.Frame(self.content_frame) + self.control_frame_middle = ttk.Frame(self.main_view) self.control_frame_middle.pack( side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 ) - self.control_frame_bottom = ttk.Frame(self.content_frame) + self.control_frame_bottom = ttk.Frame(self.main_view) self.control_frame_bottom.pack( side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 ) @@ -222,6 +229,7 @@ class PQAutomationApp( self.create_result_chart_frame() # 创建客户模板结果显示区域(黑底表格) self.create_custom_template_result_panel() + self.setup_view_stack() # 在所有控件创建完成后,统一初始化测试类型 self.root.after(100, self.initialize_default_test_type) @@ -406,17 +414,6 @@ class PQAutomationApp( self.signal_tabs.tab(i, state="normal") self.signal_tabs.select(target_tab) - self.signal_tabs.update() - self.root.update_idletasks() - - if target_tab == 0: - self.screen_module_signal_frame.tkraise() - elif target_tab == 1: - self.sdr_signal_frame.tkraise() - elif target_tab == 2: - self.hdr_signal_frame.tkraise() - elif target_tab == 3: - self.local_dimming_signal_frame.tkraise() for i in range(4): if i != target_tab: @@ -476,7 +473,19 @@ class PQAutomationApp( self._switch_signal_format_tabs(test_type) self._sync_custom_template_tab_visibility(test_type) self.sync_gamut_toolbar() - self._restore_charts_for_type(test_type) + self._schedule_chart_restore(test_type) + + def _schedule_chart_restore(self, test_type: str) -> None: + """延迟恢复图表,避免阻塞测试类型切换时的 UI 响应。""" + job = getattr(self, "_chart_restore_job", None) + if job is not None: + try: + self.root.after_cancel(job) + except Exception: + pass + self._chart_restore_job = self.root.after_idle( + lambda tt=test_type: self._restore_charts_for_type(tt) + ) def _save_chart_snapshot(self, test_type: str, chart_name: str, args: tuple): """保存某次绘图的参数,以便切换测试类型时可以重绘。""" diff --git a/settings/pq_config.json b/settings/pq_config.json index 626ade2..c94d4f9 100644 --- a/settings/pq_config.json +++ b/settings/pq_config.json @@ -1,5 +1,5 @@ { - "current_test_type": "screen_module", + "current_test_type": "local_dimming", "test_types": { "screen_module": { "name": "屏模组性能测试",