"""自定义模板结果面板(Step 6 重构)。""" import threading import time from tkinter import messagebox import tkinter as tk import ttkbootstrap as ttk import colour import numpy as np from app.data_range_converter import convert_pattern_params def create_custom_template_result_panel(self): """创建客户模板结果显示区域(黑底表格)""" self.custom_result_frame = ttk.LabelFrame( self.custom_template_tab_frame, text="客户模板结果显示" ) self.custom_result_frame.pack( side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5 ) table_container = tk.Frame( self.custom_result_frame, bg="#000000", highlightthickness=1, highlightbackground="#5a5a5a", ) table_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) style = ttk.Style() style.configure( "CustomResult.Treeview", background="#000000", fieldbackground="#000000", foreground="#ffffff", rowheight=28, borderwidth=0, ) style.configure( "CustomResult.Treeview.Heading", background="#2f2f2f", foreground="#f5f5f5", font=("Microsoft YaHei", 10, "bold"), relief="flat", ) style.map( "CustomResult.Treeview", background=[("selected", "#1f4e79")], foreground=[("selected", "#ffffff")], ) style.map( "CustomResult.Treeview.Heading", background=[("active", "#3b3b3b")], ) columns = ( "Pattern", "No.", "X", "Y", "Z", "x", "y", "Lv", "u'", "v'", "Tcp", "duv", "λd/λc", "Pe" ) self.custom_result_tree = ttk.Treeview( table_container, columns=columns, show="headings", height=4, style="CustomResult.Treeview", ) column_widths = { "Pattern": 90, "No.": 60, "X": 80, "Y": 80, "Z": 80, "x": 80, "y": 80, "Lv": 80, "u'": 80, "v'": 80, "Tcp": 90, "duv": 80, "λd/λc": 95, "Pe": 80, } for col in columns: self.custom_result_tree.heading(col, text=col) self.custom_result_tree.column( col, width=column_widths.get(col, 80), minwidth=60, anchor=tk.CENTER, stretch=False, ) y_scroll = ttk.Scrollbar( table_container, orient=tk.VERTICAL, command=self.custom_result_tree.yview, ) x_scroll = ttk.Scrollbar( table_container, orient=tk.HORIZONTAL, command=self.custom_result_tree.xview, ) self.custom_result_tree.configure( yscrollcommand=y_scroll.set, xscrollcommand=x_scroll.set, ) self.custom_result_tree.grid(row=0, column=0, sticky="nsew") y_scroll.grid(row=0, column=1, sticky="ns") x_scroll.grid(row=1, column=0, sticky="ew") # 右键菜单:复制全部数据(Excel 可直接按行列粘贴) self.custom_result_menu = tk.Menu(self.root, tearoff=0) self.custom_result_menu.add_command( label="复制全部数据", command=self.copy_custom_result_table, ) self.custom_result_menu.add_command( label="单步测试", command=self.start_custom_row_single_step, ) # self.custom_result_menu.add_separator() # self.custom_result_menu.add_command( # label="单步测试", # command=self.fill_custom_result_test_data, # ) self.custom_result_tree.bind("", self.show_custom_result_context_menu) table_container.grid_rowconfigure(0, weight=1) table_container.grid_columnconfigure(0, weight=1) def show_custom_result_context_menu(self, event): """显示客户模板结果右键菜单""" if not hasattr(self, "custom_result_tree") or not hasattr( self, "custom_result_menu" ): return if self.testing: # 测试进行中锁定客户模板结果表,禁止右键菜单。 return row_id = self.custom_result_tree.identify_row(event.y) if row_id: self.custom_result_tree.selection_set(row_id) self.custom_result_tree.focus(row_id) has_rows = len(self.custom_result_tree.get_children()) > 0 has_selection = len(self.custom_result_tree.selection()) > 0 can_single_step = ( has_selection and self.ca is not None and self.ucd is not None and not self.testing ) try: self.custom_result_menu.entryconfigure( 0, state=("normal" if has_rows else "disabled"), ) self.custom_result_menu.entryconfigure( 1, state=("normal" if can_single_step else "disabled"), ) self.custom_result_menu.tk_popup(event.x_root, event.y_root) finally: self.custom_result_menu.grab_release() def set_custom_result_table_locked(self, locked): """锁定/解锁客户模板结果表(测试期间禁选择、禁右键)""" if not hasattr(self, "custom_result_tree"): return try: self.custom_result_tree.configure(selectmode=("none" if locked else "browse")) except Exception: pass def start_custom_row_single_step(self): """单步测试当前选中行:发送该行 pattern 并覆盖该行测量结果""" if not hasattr(self, "custom_result_tree"): return if self.ca is None or self.ucd is None: messagebox.showerror("错误", "请先连接CA410和信号发生器") return if self.testing: messagebox.showinfo("提示", "测试进行中,无法执行单步测试") return selected = self.custom_result_tree.selection() if not selected: messagebox.showinfo("提示", "请先选中一行再执行单步测试") return item_id = selected[0] values = self.custom_result_tree.item(item_id, "values") if not values: messagebox.showinfo("提示", "选中行没有有效数据") return row_no = None if len(values) > 1: try: row_no = int(float(values[1])) except Exception: row_no = None if row_no is None or row_no <= 0: children = list(self.custom_result_tree.get_children()) row_no = children.index(item_id) + 1 if item_id in children else 1 _clear_custom_result_row(item_id, row_no) threading.Thread( target=_run_custom_row_single_step, args=(item_id, row_no), daemon=True, ).start() def _clear_custom_result_row(self, item_id, row_no): """单步测试开始前清空指定行的测量数据""" if not hasattr(self, "custom_result_tree"): return old_values = list(self.custom_result_tree.item(item_id, "values")) pattern_name = old_values[0] if len(old_values) > 0 else f"P {row_no}" cleared_values = ( pattern_name, row_no, "---", "---", "---", "---", "---", "---", "---", "---", "---", "---", "---", "---", ) self.custom_result_tree.item(item_id, values=cleared_values) self.custom_result_tree.see(item_id) def _run_custom_row_single_step(self, item_id, row_no): """后台执行客户模板单步测试""" try: self._dispatch_ui(self.status_var.set, f"单步测试第 {row_no} 行...") self.log_gui.log(f"开始单步测试第 {row_no} 行", level="info") self.config.set_current_pattern("custom") # 与批量 custom 测试保持一致:根据当前 SDR 配置转换 pattern 数据。 import copy data_range = self.sdr_data_range_var.get() original_params = copy.deepcopy(self.config.default_pattern_temp["pattern_params"]) converted_params = convert_pattern_params( pattern_params=original_params, data_range=data_range, verbose=False, ) temp_config = self.config.get_temp_config_with_converted_params( mode="custom", converted_params=converted_params, ) if row_no > len(converted_params): self.log_gui.log(f"行号超出 pattern 范围: {row_no}/{len(converted_params)}", level="error") self._dispatch_ui(self.status_var.set, "单步测试失败:行号超范围") return self.ucd.set_ucd_params(temp_config) pattern_param = converted_params[row_no - 1] self.ucd.set_pattern(self.ucd.current_pattern, pattern_param) self.ucd.run() time.sleep(self.pattern_settle_time) # 测量:显示模式1读取 Tcp/duv/Lv,显示模式8读取 λd/Pe/Lv 与 XYZ。 self.ca.set_Display(1) tcp, duv, lv, _, _, _ = self.ca.readAllDisplay() self.ca.set_Display(8) lambda_d, pe, lv, X, Y, Z = self.ca.readAllDisplay() xy = colour.XYZ_to_xy(np.array([X, Y, Z])) u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS(np.array([X, Y, Z])) row_data = { "X": X, "Y": Y, "Z": Z, "x": xy[0], "y": xy[1], "Lv": lv, "u_prime": u_prime, "v_prime": v_prime, "Tcp": tcp, "duv": duv, "lambda_d": lambda_d, "Pe": pe, } self._dispatch_ui( _update_custom_result_row, item_id, row_no, row_data ) self.log_gui.log(f"第 {row_no} 行单步测试完成并已覆盖", level="success") self._dispatch_ui(self.status_var.set, f"第 {row_no} 行单步测试完成") except Exception as e: self.log_gui.log(f"单步测试失败: {str(e)}", level="error") self._dispatch_ui(self.status_var.set, "单步测试失败") def _update_custom_result_row(self, item_id, row_no, result_data): """覆盖更新客户模板结果表中指定行""" def fmt(value, digits=4): if value is None: return "--" if isinstance(value, (int, float, np.floating)): # CA 返回异常哨兵值(如 -99999999)时,显示为占位符。 if (not np.isfinite(value)) or value <= -99999998: return "---" return f"{value:.{digits}f}" try: numeric_value = float(value) if (not np.isfinite(numeric_value)) or numeric_value <= -99999998: return "---" except (TypeError, ValueError): pass return str(value) old_values = list(self.custom_result_tree.item(item_id, "values")) pattern_name = old_values[0] if len(old_values) > 0 else f"P {row_no}" new_values = ( pattern_name, row_no, fmt(result_data.get("X")), fmt(result_data.get("Y")), fmt(result_data.get("Z")), fmt(result_data.get("x")), fmt(result_data.get("y")), fmt(result_data.get("Lv"), 3), fmt(result_data.get("u_prime")), fmt(result_data.get("v_prime")), fmt(result_data.get("Tcp"), 1), fmt(result_data.get("duv"), 5), fmt(result_data.get("lambda_d"), 1), fmt(result_data.get("Pe"), 1), ) self.custom_result_tree.item(item_id, values=new_values) def copy_custom_result_table(self): """复制客户模板结果表格到剪贴板(不含标题行/No./Pattern)""" if not hasattr(self, "custom_result_tree"): return items = self.custom_result_tree.get_children() if not items: messagebox.showinfo("提示", "当前没有可复制的数据") return lines = [] columns = tuple(self.custom_result_tree["columns"]) excluded_col_indexes = { idx for idx, col_name in enumerate(columns) if col_name in ("No.", "Pattern") } for item in items: values = self.custom_result_tree.item(item, "values") # 跳过 No. 和 Pattern 两列,只保留测量数据列。 data_values = [ v for idx, v in enumerate(values) if idx not in excluded_col_indexes ] row = [ str(v).replace("\t", " ").replace("\n", " ") for v in data_values ] lines.append("\t".join(row)) clipboard_text = "\n".join(lines) self.root.clipboard_clear() self.root.clipboard_append(clipboard_text) self.root.update_idletasks() if hasattr(self, "status_var"): self.status_var.set(f"已复制 {len(items)} 行客户模板数据到剪贴板") if hasattr(self, "log_gui"): self.log_gui.log(f"已复制客户模板表格数据({len(items)} 行)", level="success") def clear_custom_template_results(self): """清空客户模板结果表格""" if not hasattr(self, "custom_result_tree"): return for item in self.custom_result_tree.get_children(): self.custom_result_tree.delete(item) def auto_expand_custom_result_view(self): """当客户模板表格有数据时,自动扩展窗口以尽量完整显示所有列""" if not hasattr(self, "custom_result_tree"): return if len(self.custom_result_tree.get_children()) == 0: return try: self.root.update_idletasks() columns = tuple(self.custom_result_tree["columns"]) columns_total_width = 0 for col in columns: columns_total_width += int(self.custom_result_tree.column(col, "width")) left_panel_width = self.left_frame.winfo_width() if hasattr(self, "left_frame") else 180 if left_panel_width <= 1: left_panel_width = 180 # 列宽 + 左侧导航 + 滚动条/边框/外边距。 target_width = int(left_panel_width + columns_total_width + 120) screen_max_width = max(900, self.root.winfo_screenwidth() - 40) target_width = min(target_width, screen_max_width) current_width = self.root.winfo_width() current_height = self.root.winfo_height() # 只扩不缩,避免用户窗口被反复改变。 if target_width > current_width: self.root.geometry(f"{target_width}x{current_height}") self.root.update_idletasks() except Exception as e: if hasattr(self, "log_gui"): self.log_gui.log(f"自动扩展客户模板窗口失败: {str(e)}", level="error") def append_custom_template_result(self, row_no, result_data): """追加一条客户模板结果到表格""" def fmt(value, digits=4): if value is None: return "--" if isinstance(value, (int, float, np.floating)): # CA 返回异常哨兵值(如 -99999999)时,显示为占位符。 if (not np.isfinite(value)) or value <= -99999998: return "---" return f"{value:.{digits}f}" try: numeric_value = float(value) if (not np.isfinite(numeric_value)) or numeric_value <= -99999998: return "---" except (TypeError, ValueError): pass return str(value) row_values = ( result_data.get("pattern_name", f"P {row_no}"), row_no, fmt(result_data.get("X")), fmt(result_data.get("Y")), fmt(result_data.get("Z")), fmt(result_data.get("x")), fmt(result_data.get("y")), fmt(result_data.get("Lv"), 3), fmt(result_data.get("u_prime")), fmt(result_data.get("v_prime")), fmt(result_data.get("Tcp"), 1), fmt(result_data.get("duv"), 5), fmt(result_data.get("lambda_d"), 1), fmt(result_data.get("Pe"), 1) ) if hasattr(self, "custom_result_tree"): item_id = self.custom_result_tree.insert("", tk.END, values=row_values) # 新增数据后自动跳转到最新行。 self.custom_result_tree.see(item_id) self.auto_expand_custom_result_view() def start_custom_template_test(self): """开始客户模板测试(SDR)""" if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"): self.chart_notebook.select(self.custom_template_tab_frame) if self.ca is None or self.ucd is None: messagebox.showerror("错误", "请先连接CA410和信号发生器") return if self.testing: messagebox.showinfo("提示", "测试已在进行中") return if hasattr(self, "debug_container"): self.debug_container.pack_forget() self.testing = True self.start_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) self.save_btn.config(state=tk.DISABLED) self.clear_config_btn.config(state=tk.DISABLED) self.custom_btn.config(state=tk.DISABLED) self.status_var.set("客户模板测试进行中...") self.log_gui.clear_log() self.clear_custom_template_results() confirm = messagebox.askyesno( "确认测试", "开始客户模板测试(SDR)?\n\n将采集并显示客户模板格式结果。" ) if not confirm: self.testing = False self.start_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) self.clear_config_btn.config(state=tk.NORMAL) self.custom_btn.config(state=tk.NORMAL) self.status_var.set("测试已取消") self.set_custom_result_table_locked(False) return self.set_custom_result_table_locked(True) self.test_thread = threading.Thread(target=self.run_custom_sdr_test, args=([],)) self.test_thread.daemon = True self.test_thread.start() def update_custom_button_visibility(self): """只在 SDR 测试时显示客户模版按钮""" if not hasattr(self, "custom_btn") or not hasattr(self, "test_type_var"): return if self.test_type_var.get() == "sdr_movie": if not self.custom_btn.winfo_manager(): self.custom_btn.pack(side=tk.LEFT, padx=5) else: if self.custom_btn.winfo_manager(): self.custom_btn.pack_forget() # def fill_custom_result_test_data(self): # """填充 147 行客户模板测试数据(用于界面验证)""" # if not hasattr(self, "custom_result_tree"): # return # self.clear_custom_template_results() # pattern_names = [] # if hasattr(self, "config") and hasattr(self.config, "get_temp_pattern_names"): # pattern_names = self.config.get_temp_pattern_names() # total_rows = 147 # for i in range(1, total_rows + 1): # ratio = (i - 1) / (total_rows - 1) if total_rows > 1 else 0 # row_data = { # "pattern_name": ( # pattern_names[i - 1] if i - 1 < len(pattern_names) else f"P {i}" # ), # "X": 0.8 + ratio * 120, # "Y": 0.9 + ratio * 135, # "Z": 1.1 + ratio * 145, # "x": 0.24 + ratio * 0.10, # "y": 0.26 + ratio * 0.10, # "Lv": 1.0 + ratio * 500, # "u_prime": 0.16 + ratio * 0.12, # "v_prime": 0.42 + ratio * 0.08, # "Tcp": 1800 + ratio * 12000, # "duv": -0.01 + ratio * 0.03, # "lambda_d": 430 + ratio * 200, # "Pe": 10 + ratio * 90, # } # self.append_custom_template_result(i, row_data) # if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"): # self.chart_notebook.select(self.custom_template_tab_frame) # if hasattr(self, "status_var"): # self.status_var.set("已填充 147 行客户模板测试数据") # if hasattr(self, "log_gui"): # self.log_gui.log("已填充 147 行客户模板测试数据", level="success")