让对话框对UPDATE_COMMAND_UI生效

博客指出UPDATE_COMMAND_UI在基于对话框的菜单中修改状态无效,原因是对话框程序无OnInitMenuPopup函数,不会调用UPDATE_COMMAND_UI响应函数。并给出解决方法,包括在对话框类的.cpp文件添加消息映射入口、在.h文件添加消息函数声明及在.cpp文件添加函数代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:一般情况下我们用UPDATE_COMMAND_UI来修改菜单的状态(enable/disable, check/uncheck, change text),但这个方法在一个基于对话框上的菜单却没有效果。
void CTestDlg::OnUpdateFileExit(CCmdUI* pCmdUI)
{
     pCmdUI->Enable(FALSE);
     pCmdUI->SetCheck(TRUE);
     pCmdUI->SetRadio(TRUE);
     pCmdUI->SetText("Close");
//以上方法在MDI、SDI程序中都能起作用,在对话框中却没有效果,根本没有调用这个函数。
}
 
原因分析:当显示一个下拉的菜单的时候,在显示菜单前会发送WM_INITMENUPOPUP消息。而CFrameWnd::OnInitMenuPopup函数会刷新这个菜单项,同时如果有UPDATE_COMMAND_UI响应函数,则调用它。通过它来更新反应每个菜单的外观效果(enabled/disabled, checked/unchecked).
在一个基于对话框的程序中,因为没有OnInitMenuPopup函数,所以不会调用UPDATE_COMMAND_UI响应函数,而是使用了CWnd类的默认处理, 这种处理没有调用UPDATE_COMMAND_UI响应函数。

解决方法如下:
第一步:
在对话框类的.cpp文件,添加一个ON_WM_INITMENUPOPUP入口到消息映射里面
BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
//}}AFX_MSG_MAP
ON_WM_INITMENUPOPUP()
END_MESSAGE_MAP()
第二步:
在对话框类的.h文件添加消息函数声明。
// Generated message map functions
//{{AFX_MSG(CDisableDlgMenuDlg)
afx_msg void OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
第三步:
在对话框类的.cpp文件添加如下函数代码(大部分代码取自WinFrm.cpp文件的函数CFrameWnd::OnInitMenuPopup):
void C******Dlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu)
{
     ASSERT(pPopupMenu != NULL);
    // Check the enabled state of various menu items.

    CCmdUI state;
    state.m_pMenu = pPopupMenu;
    ASSERT(state.m_pOther == NULL);
    ASSERT(state.m_pParentMenu == NULL);

    // Determine if menu is popup in top-level menu and set m_pOther to
    // it if so (m_pParentMenu == NULL indicates that it is secondary popup).
    HMENU hParentMenu;
    if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu)
         state.m_pParentMenu = pPopupMenu;??? // Parent == child for tracking popup.
   else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)
   {
       CWnd* pParent = this;
       // Child windows don't have menus--need to go to the top!
       if (pParent != NULL &&
(hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL)
      {
           int nIndexMax = ::GetMenuItemCount(hParentMenu);
          for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
         {
             if (::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu)
             {
                // When popup is found, m_pParentMenu is containing menu.
                state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
                break;
             }
         }
    }
}

  state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
  for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
  state.m_nIndex++)
  {
      state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
      if (state.m_nID == 0)
          continue; // Menu separator or invalid cmd - ignore it.

      ASSERT(state.m_pOther == NULL);
      ASSERT(state.m_pMenu != NULL);
      if (state.m_nID == (UINT)-1)
      {
          // Possibly a popup menu, route to first item of that popup.
          state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
          if (state.m_pSubMenu == NULL ||
 (state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
state.m_nID == (UINT)-1)
       {
              continue;    // First item of popup can't be routed to.
       }
       state.DoUpdate(this, TRUE);?? // Popups are never auto disabled.
      }
      else
     {
          // Normal menu item.
          // Auto enable/disable if frame window has m_bAutoMenuEnable
          // set and command is _not_ a system command.
         state.m_pSubMenu = NULL;
         state.DoUpdate(this, FALSE);
      }

      // Adjust for menu deletions and additions.
      UINT nCount = pPopupMenu->GetMenuItemCount();
      if (nCount < state.m_nIndexMax)
        {
                   state.m_nIndex -= (state.m_nIndexMax - nCount);
                   while (state.m_nIndex < nCount &&
                  pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
           {
            state.m_nIndex++;
            }
       }
        state.m_nIndexMax = nCount;
   }
}

更详细的信息可参考
http://support.microsoft.com/default.aspx?scid=kb;en-us;242577

import tkinter as tk from tkinter import filedialog, messagebox, ttk import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk from matplotlib.widgets import Slider, TextBox import struct import os import re import logging # 配置日志系统 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # 设置matplotlib中文字体支持 plt.rcParams["font.family"] = ["SimHei"] plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 class BinaryVisualizer: def __init__(self): self.root = None self.menubar = None # 显式引用菜单栏 self.files = {} # 存储已加载的文件数据 self.current_file_alias = None self.current_frame = 0 self.width = 512 # 默认宽度 self.height = 341 # 默认高度 self.block_size = self.width * self.height # 每帧的像素数 self.value_offset = 0 # 数据偏移值 self.display_mode = "固定范围" # 默认显示模式 self.custom_min = -1000 # 默认自定义最小值 self.custom_max = 1000 # 默认自定义最大值 self.operations = {} # 存储运算结果 self.current_display_source = "original" # 当前显示源: "original" 或 "operation" self.selected_operation = None # 当前选择的运算结果 # 允许的公式字符集合 self.allowed_formula_chars = set(['+', '-', '*', '/', '(', ')', 'numpy.', 'np.', '.', '_', ' ']) self.allowed_formula_chars.update([str(i) for i in range(10)]) # 初始化matplotlib设置 plt.rcParams["figure.figsize"] = (10, 7) plt.rcParams["image.cmap"] = "gray" def detect_possible_resolutions(self, pixel_count): """检测可能的分辨率""" possible_resolutions = [] max_width = int(np.sqrt(pixel_count)) * 2 for width in range(100, max_width, 2): # 步长为2,确保宽度是偶数 if pixel_count % width == 0: height = pixel_count // width possible_resolutions.append((width, height)) return possible_resolutions def load_data(self, file_path=None, alias=None): """加载二进制数据文件""" if not file_path: file_path = filedialog.askopenfilename( title="选择二进制数据文件", filetypes=[("二进制文件", "*.dat"), ("所有文件", "*.*")] ) if not file_path: return False # 自动生成别称(A, B, C, ...) if not alias: used_aliases = set(self.files.keys()) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': if c not in used_aliases: alias = c break else: messagebox.showerror("错误", "无法生成文件别称,已达到最大限制") return False try: with open(file_path, 'rb') as bin_file: data = bin_file.read() total_bytes = len(data) # 每像素2字节,计算可能的帧数 pixel_count = total_bytes // 2 possible_frames = pixel_count // self.block_size if possible_frames == 0: # 尝试检测可能的分辨率 possible_resolutions = self.detect_possible_resolutions(pixel_count) if not possible_resolutions: messagebox.showerror("错误", f"文件太小,无法包含一个完整的{self.width}x{self.height}帧") return False # 让用户选择分辨率 resolution_window = tk.Toplevel(self.root) resolution_window.title("选择分辨率") tk.Label(resolution_window, text="无法使用默认分辨率,请选择或输入:").pack(padx=10, pady=10) resolution_frame = tk.Frame(resolution_window) resolution_frame.pack(padx=10, pady=5) tk.Label(resolution_frame, text="宽度:").pack(side=tk.LEFT, padx=5) width_entry = tk.Entry(resolution_frame, width=10) width_entry.insert(0, str(self.width)) width_entry.pack(side=tk.LEFT, padx=5) tk.Label(resolution_frame, text="高度:").pack(side=tk.LEFT, padx=5) height_entry = tk.Entry(resolution_frame, width=10) height_entry.insert(0, str(self.height)) height_entry.pack(side=tk.LEFT, padx=5) def apply_resolution(): try: self.width = int(width_entry.get()) self.height = int(height_entry.get()) self.block_size = self.width * self.height resolution_window.destroy() self.load_data(file_path, alias) # 重新加载 except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(resolution_window, text="应用", command=apply_resolution).pack(pady=10) return False logger.info(f"文件大小: {total_bytes} 字节") logger.info(f"可能的帧数: {possible_frames}") # 解析每一帧数据 frames = [] for frame_idx in range(possible_frames): start = frame_idx * self.block_size * 2 # 每像素2字节 end = start + self.block_size * 2 frame_data = data[start:end] # 确保帧大小是偶数 if len(frame_data) % 2 != 0: logger.warning(f"帧 {frame_idx+1} 字节数为奇数,最后一个字节将被忽略") frame_data = frame_data[:-1] # 解析为有符号整数并应用偏移 frame_values = [] for i in range(0, len(frame_data), 2): low_byte = frame_data[i] high_byte = frame_data[i+1] try: signed_value = struct.unpack('<h', bytes([low_byte, high_byte]))[0] except struct.error as e: logger.error(f"解析帧 {frame_idx+1} 时出错: {e}") signed_value = 0 # 默认值 adjusted_value = signed_value + self.value_offset frame_values.append((signed_value, adjusted_value)) # 转换为二维数组 try: signed_array = np.array([x[0] for x in frame_values]).reshape((self.height, self.width)) adjusted_array = np.array([x[1] for x in frame_values]).reshape((self.height, self.width)) frames.append((signed_array, adjusted_array)) except ValueError as e: logger.error(f"重塑帧 {frame_idx+1} 时出错: {e}") # 添加空白帧作为替代 frames.append((np.zeros((self.height, self.width)), np.zeros((self.height, self.width)))) # 存储文件数据 self.files[alias] = { 'path': file_path, 'data': data, 'frames': frames } # 如果是第一个文件或明确指定了当前文件 if not self.current_file_alias or alias == self.current_file_alias: self.current_file_alias = alias self.current_frame = 0 # 更新UI if self.slider_frame: self.slider_frame.valmax = len(frames) - 1 self.slider_frame.set_val(0) self.slider_frame.set_active(True) # 启用滑块 if self.textbox_frame: self.textbox_frame.set_val("1") self.update_display_range() # 更新文件选择菜单 self.update_file_menu() logger.info(f"成功加载文件 {alias}: {os.path.basename(file_path)}") return True except Exception as e: messagebox.showerror("错误", f"加载文件时出错: {str(e)}") logger.error(f"加载文件时出错: {e}", exc_info=True) return False def load_multiple_files(self): """加载多个二进制数据文件""" file_paths = filedialog.askopenfilenames( title="选择多个二进制数据文件", filetypes=[("二进制文件", "*.dat"), ("所有文件", "*.*")] ) if not file_paths: return for file_path in file_paths: self.load_data(file_path) def select_file(self, alias): """选择当前文件""" if alias in self.files: self.current_file_alias = alias self.current_frame = 0 # 更新UI if self.slider_frame: self.slider_frame.valmax = len(self.files[alias]['frames']) - 1 self.slider_frame.set_val(0) if self.textbox_frame: self.textbox_frame.set_val("1") self.update_display_range() def on_frame_change(self, val): """帧滑块值改变时调用""" frame_idx = int(val) if self.current_file_alias and self.current_file_alias in self.files: max_frame = len(self.files[self.current_file_alias]['frames']) - 1 if frame_idx > max_frame: frame_idx = max_frame self.slider_frame.set_val(frame_idx) self.current_frame = frame_idx self.textbox_frame.set_val(str(frame_idx + 1)) self.update_display_range() def on_frame_number_submit(self, text): """帧号输入提交时调用""" try: frame_num = int(text) if self.current_file_alias and self.current_file_alias in self.files: max_frame = len(self.files[self.current_file_alias]['frames']) if frame_num < 1 or frame_num > max_frame: messagebox.showinfo("提示", f"请输入1到{max_frame}之间的帧号") frame_num = min(max(frame_num, 1), max_frame) self.current_frame = frame_num - 1 self.slider_frame.set_val(self.current_frame) self.update_display_range() except ValueError: messagebox.showerror("错误", "请输入有效的整数") def set_display_mode(self, mode): """设置显示模式""" self.display_mode = mode self.update_display_range() def set_custom_range(self): """设置自定义显示范围""" range_window = tk.Toplevel(self.root) range_window.title("设置自定义范围") tk.Label(range_window, text="最小值:").pack(padx=10, pady=5) min_entry = tk.Entry(range_window, width=10) min_entry.insert(0, str(self.custom_min)) min_entry.pack(padx=10) tk.Label(range_window, text="最大值:").pack(padx=10, pady=5) max_entry = tk.Entry(range_window, width=10) max_entry.insert(0, str(self.custom_max)) max_entry.pack(padx=10) def apply_range(): try: self.custom_min = int(min_entry.get()) self.custom_max = int(max_entry.get()) if self.custom_min >= self.custom_max: messagebox.showerror("错误", "最小值必须小于最大值") return self.display_mode = "自定义" self.update_display_range() range_window.destroy() except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(range_window, text="应用", command=apply_range).pack(pady=10) def update_display_range(self): """更新显示范围""" if not self.current_file_alias or self.current_file_alias not in self.files: return frames = self.files[self.current_file_alias]['frames'] if not frames or self.current_frame >= len(frames): return current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 try: if self.display_mode == "固定范围": vmin = self.custom_min vmax = self.custom_max elif self.display_mode == "帧平均±1000": frame_mean = np.mean(current_frame_data) vmin = max(frame_mean - 1000, np.min(current_frame_data)) vmax = min(frame_mean + 1000, np.max(current_frame_data)) elif self.display_mode == "帧平均±200": frame_mean = np.mean(current_frame_data) vmin = max(frame_mean - 200, np.min(current_frame_data)) vmax = min(frame_mean + 200, np.max(current_frame_data)) else: # 自定义 vmin = self.custom_min vmax = self.custom_max # 更新图像显示 self.current_image.set_data(current_frame_data) self.current_image.set_clim(vmin, vmax) # 更新直方图 self.ax_hist.clear() self.ax_hist.hist(current_frame_data.flatten(), bins=50, range=(vmin, vmax), alpha=0.7) self.ax_hist.set_title("像素值分布直方图") self.ax_hist.set_xlabel("像素值") self.ax_hist.set_ylabel("频次") self.ax_hist.grid(True) # 更新标题 frame_mean = np.mean(current_frame_data) frame_std = np.std(current_frame_data) # 安全处理文件名 try: if self.current_file_alias in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': file_name = f"文件 {self.current_file_alias}" else: file_name = self.current_file_alias except Exception as e: logger.error(f"处理文件名时出错: {e}") file_name = "未知文件" self.fig.suptitle(f"{file_name} - 帧 {self.current_frame+1}/{len(frames)} - 均值: {frame_mean:.2f}, 标准差: {frame_std:.2f}") self.canvas.draw() except Exception as e: logger.error(f"更新显示范围时出错: {e}") messagebox.showerror("显示错误", f"更新显示时出错: {str(e)}") def export_to_txt(self): """导出当前帧为TXT文件""" if not self.current_file_alias or self.current_file_alias not in self.files: messagebox.showinfo("提示", "请先加载文件") return frames = self.files[self.current_file_alias]['frames'] if not frames or self.current_frame >= len(frames): messagebox.showinfo("提示", "无效的帧") return current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 file_path = filedialog.asksaveasfilename( title="保存当前帧为文本文件", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return try: with open(file_path, 'w') as txt_file: # 写入头部信息 txt_file.write(f"# 数据导出 - 文件: {self.current_file_alias}\n") txt_file.write(f"# 帧号: {self.current_frame+1}/{len(frames)}\n") txt_file.write(f"# 分辨率: {self.width}x{self.height}\n") txt_file.write(f"# 像素值范围: {np.min(current_frame_data)} - {np.max(current_frame_data)}\n") txt_file.write(f"# 均值: {np.mean(current_frame_data):.2f}, 标准差: {np.std(current_frame_data):.2f}\n") txt_file.write("#\n") # 写入数据 for row in current_frame_data: row_str = ' '.join([f"{float(val):.6f}" for val in row]) txt_file.write(row_str + '\n') messagebox.showinfo("成功", f"帧数据已保存到 {file_path}") except Exception as e: messagebox.showerror("错误", f"保存文件时出错: {str(e)}") def set_resolution(self): """设置图像分辨率""" resolution_window = tk.Toplevel(self.root) resolution_window.title("设置分辨率") tk.Label(resolution_window, text="宽度:").pack(padx=10, pady=5) width_entry = tk.Entry(resolution_window, width=10) width_entry.insert(0, str(self.width)) width_entry.pack(padx=10) tk.Label(resolution_window, text="高度:").pack(padx=10, pady=5) height_entry = tk.Entry(resolution_window, width=10) height_entry.insert(0, str(self.height)) height_entry.pack(padx=10) def apply_resolution(): try: new_width = int(width_entry.get()) new_height = int(height_entry.get()) if new_width <= 0 or new_height <= 0: messagebox.showerror("错误", "宽度和高度必须为正整数") return self.width = new_width self.height = new_height self.block_size = self.width * self.height # 重新加载当前文件以应用新的分辨率 if self.current_file_alias and self.current_file_alias in self.files: file_path = self.files[self.current_file_alias]['path'] self.load_data(file_path, self.current_file_alias) resolution_window.destroy() except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(resolution_window, text="应用", command=apply_resolution).pack(pady=10) def set_offset(self): """设置数据偏移值""" offset_window = tk.Toplevel(self.root) offset_window.title("设置偏移值") tk.Label(offset_window, text="偏移值:").pack(padx=10, pady=5) offset_entry = tk.Entry(offset_window, width=10) offset_entry.insert(0, str(self.value_offset)) offset_entry.pack(padx=10) def apply_offset(): try: self.value_offset = int(offset_entry.get()) # 重新加载当前文件以应用新的偏移值 if self.current_file_alias and self.current_file_alias in self.files: file_path = self.files[self.current_file_alias]['path'] self.load_data(file_path, self.current_file_alias) offset_window.destroy() except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(offset_window, text="应用", command=apply_offset).pack(pady=10) def create_formula(self): """创建公式窗口""" if len(self.files) < 2: messagebox.showinfo("提示", "请先加载至少两个文件") return formula_window = tk.Toplevel(self.root) formula_window.title("公式计算") formula_window.geometry("600x400") # 可用文件列表 tk.Label(formula_window, text="可用文件:").pack(padx=10, pady=5, anchor="w") files_frame = tk.Frame(formula_window) files_frame.pack(padx=10, fill="x") file_listbox = tk.Listbox(files_frame, width=50) file_listbox.pack(side=tk.LEFT, fill="both", expand=True) scrollbar = tk.Scrollbar(files_frame, orient="vertical", command=file_listbox.yview) scrollbar.pack(side=tk.RIGHT, fill="y") file_listbox.config(yscrollcommand=scrollbar.set) for alias in sorted(self.files.keys()): file_listbox.insert(tk.END, f"文件 {alias}: {os.path.basename(self.files[alias]['path'])}") # 公式输入 tk.Label(formula_window, text="公式输入 (使用文件别称如 A,B,C 进行 +-*/ 运算):").pack(padx=10, pady=5, anchor="w") formula_frame = tk.Frame(formula_window) formula_frame.pack(padx=10, fill="x") formula_entry = tk.Entry(formula_frame, width=50) formula_entry.pack(side=tk.LEFT, fill="x", expand=True) # 插入文件按钮 def insert_file_alias(): selection = file_listbox.curselection() if selection: alias = sorted(self.files.keys())[selection[0]] formula_entry.insert(tk.INSERT, alias) tk.Button(formula_frame, text="插入文件", command=insert_file_alias).pack(side=tk.LEFT, padx=5) # 帧选择 tk.Label(formula_window, text="选择帧:").pack(padx=10, pady=5, anchor="w") frame_frame = tk.Frame(formula_window) frame_frame.pack(padx=10, fill="x") frame_var = tk.StringVar(value="same") frame_options = [ ("所有文件使用相同帧", "same"), ("为每个文件指定帧", "different") ] for text, value in frame_options: tk.Radiobutton(frame_frame, text=text, variable=frame_var, value=value).pack(anchor="w") # 相同帧选择 same_frame_frame = tk.Frame(formula_window) same_frame_frame.pack(padx=10, pady=5, fill="x") tk.Label(same_frame_frame, text="帧号:").pack(side=tk.LEFT) same_frame_entry = tk.Entry(same_frame_frame, width=10) same_frame_entry.insert(0, "1") same_frame_entry.pack(side=tk.LEFT, padx=5) # 结果名称 tk.Label(formula_window, text="结果名称:").pack(padx=10, pady=5, anchor="w") result_name_frame = tk.Frame(formula_window) result_name_frame.pack(padx=10, fill="x") result_name_entry = tk.Entry(result_name_frame, width=20) result_name_entry.insert(0, "Result") result_name_entry.pack(side=tk.LEFT, padx=5) # 计算按钮 def calculate_formula(): formula = formula_entry.get().strip() if not formula: messagebox.showerror("错误", "请输入公式") return # 验证公式 valid_aliases = set(self.files.keys()) used_aliases = set(re.findall(r'[A-Z]', formula)) if not used_aliases.issubset(valid_aliases): invalid_aliases = used_aliases - valid_aliases messagebox.showerror("错误", f"公式中包含无效的文件别称: {', '.join(invalid_aliases)}") return # 额外验证公式安全性 if not self._validate_formula(formula): return try: frame_mode = frame_var.get() if frame_mode == "same": try: frame_num = int(same_frame_entry.get()) except ValueError: messagebox.showerror("错误", "请输入有效的帧号") return # 检查所有文件是否有该帧 for alias in used_aliases: if frame_num < 1 or frame_num > len(self.files[alias]['frames']): messagebox.showerror("错误", f"文件 {alias} 没有帧 {frame_num}") return # 创建变量字典 variables = {} for alias in used_aliases: variables[alias] = self.files[alias]['frames'][frame_num-1][1] # 使用偏移后的数据 # 计算结果 try: result = self.safe_eval(formula, variables) if result is None: raise ValueError("公式计算失败") except Exception as e: messagebox.showerror("错误", f"公式计算出错: {str(e)}") return # 保存结果 result_name = result_name_entry.get().strip() if not result_name: result_name = "Result" # 检查结果名称是否已存在 if result_name in self.operations: if not messagebox.askyesno("确认", f"结果名称 '{result_name}' 已存在,是否覆盖?"): return # 保存结果 self.operations[result_name] = { 'formula': formula, 'frame_mode': frame_mode, 'frame_num': frame_num if frame_mode == "same" else None, 'result': result } messagebox.showinfo("成功", f"公式计算成功,结果已保存为 '{result_name}'") formula_window.destroy() # 更新操作菜单 self.update_operations_menu() except Exception as e: messagebox.showerror("错误", f"执行计算时出错: {str(e)}") logger.error(f"执行计算时出错: {e}", exc_info=True) tk.Button(formula_window, text="计算", command=calculate_formula).pack(pady=10) def safe_eval(self, formula, variables): """安全执行公式计算""" # 验证公式 for char in formula: if char not in self.allowed_formula_chars: logger.warning(f"公式包含不允许的字符: {char}") return None # 禁用所有内置函数,只允许numpy return eval(formula, {'__builtins__': None, 'np': np, 'numpy': np}, variables) def _validate_formula(self, formula): """验证公式安全性""" for char in formula: if char not in self.allowed_formula_chars: messagebox.showerror("错误", f"公式包含不允许的字符: {char}") logger.error(f"非法公式: {formula}") return False return True def show_operation_result(self, name): """显示运算结果""" if name in self.operations: self.selected_operation = name result = self.operations[name]['result'] # 更新图像显示 self.current_image.set_data(result) # 更新显示范围 vmin = np.min(result) vmax = np.max(result) self.current_image.set_clim(vmin, vmax) # 更新直方图 self.ax_hist.clear() self.ax_hist.hist(result.flatten(), bins=50, range=(vmin, vmax), alpha=0.7) self.ax_hist.set_title("运算结果像素值分布") self.ax_hist.set_xlabel("像素值") self.ax_hist.set_ylabel("频次") self.ax_hist.grid(True) # 更新标题 formula = self.operations[name]['formula'] self.fig.suptitle(f"运算结果 - {name}: {formula}") self.canvas.draw() self.current_display_source = "operation" self.source_var.set("运算结果") # 更新数据源菜单 self._update_source_menu() def save_operation_result(self): """保存当前运算结果为TXT文件""" if not self.operations: messagebox.showinfo("提示", "没有可用的运算结果") return # 获取当前显示的运算结果名称 current_name = None if self.current_display_source == "operation" and self.selected_operation: current_name = self.selected_operation # 如果没有当前显示的结果,让用户选择 if not current_name or current_name not in self.operations: if len(self.operations) == 1: current_name = list(self.operations.keys())[0] else: # 创建选择窗口 select_window = tk.Toplevel(self.root) select_window.title("选择运算结果") tk.Label(select_window, text="请选择要保存的运算结果:").pack(padx=10, pady=10) result_listbox = tk.Listbox(select_window, width=50) result_listbox.pack(padx=10, fill="both", expand=True) for name in sorted(self.operations.keys()): result_listbox.insert(tk.END, f"{name}: {self.operations[name]['formula']}") def on_select(): selection = result_listbox.curselection() if selection: selected_name = sorted(self.operations.keys())[selection[0]] select_window.destroy() self._save_result_to_txt(selected_name) button_frame = tk.Frame(select_window) button_frame.pack(pady=10) tk.Button(button_frame, text="确定", command=on_select).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="取消", command=select_window.destroy).pack(side=tk.LEFT, padx=10) return else: self._save_result_to_txt(current_name) def _save_result_to_txt(self, name): """实际保存运算结果到TXT文件,支持多种格式""" result = self.operations[name]['result'] file_path = filedialog.asksaveasfilename( title="保存运算结果为文本文件", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return # 选择保存格式 format_window = tk.Toplevel(self.root) format_window.title("选择保存格式") format_var = tk.StringVar(value="decimal") format_frame = tk.Frame(format_window) format_frame.pack(padx=10, pady=10) format_options = [ ("带符号十进制数", "signed"), ("十进制数", "decimal"), ("十六进制", "hex") ] for text, value in format_options: tk.Radiobutton(format_frame, text=text, variable=format_var, value=value).pack(anchor="w", pady=2) def save_with_format(): try: format_type = format_var.get() format_window.destroy() with open(file_path, 'w') as txt_file: # 写入公式信息 txt_file.write(f"# 运算公式: {self.operations[name]['formula']}\n") txt_file.write(f"# 保存格式: {dict(format_options).get(format_type, format_type)}\n") txt_file.write("#\n") # 写入数据 for row in result: if format_type == "hex": # 转换为十六进制格式 row_str = ' '.join([f"{int(val):04X}" for val in row]) elif format_type == "signed": # 带符号十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) else: # decimal # 十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) txt_file.write(row_str + '\n') messagebox.showinfo("成功", f"运算结果已保存到 {file_path}") except Exception as e: messagebox.showerror("错误", f"保存文件时出错: {str(e)}") tk.Button(format_window, text="保存", command=save_with_format).pack(pady=10) tk.Button(format_window, text="取消", command=format_window.destroy).pack(pady=10) def export_to_txt(self): """导出当前帧为TXT文件,支持多种格式""" if not self.current_file_alias or self.current_file_alias not in self.files: messagebox.showinfo("提示", "请先加载文件") return frames = self.files[self.current_file_alias]['frames'] if not frames or self.current_frame >= len(frames): messagebox.showinfo("提示", "无效的帧") return current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 file_path = filedialog.asksaveasfilename( title="保存当前帧为文本文件", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return # 选择保存格式 format_window = tk.Toplevel(self.root) format_window.title("选择保存格式") format_var = tk.StringVar(value="decimal") format_frame = tk.Frame(format_window) format_frame.pack(padx=10, pady=10) format_options = [ ("带符号十进制数", "signed"), ("十进制数", "decimal"), ("十六进制", "hex") ] for text, value in format_options: tk.Radiobutton(format_frame, text=text, variable=format_var, value=value).pack(anchor="w", pady=2) def save_with_format(): try: format_type = format_var.get() format_window.destroy() with open(file_path, 'w') as txt_file: # 写入头部信息 txt_file.write(f"# 数据导出 - 文件: {self.current_file_alias}\n") txt_file.write(f"# 帧号: {self.current_frame+1}/{len(frames)}\n") txt_file.write(f"# 分辨率: {self.width}x{self.height}\n") txt_file.write(f"# 像素值范围: {np.min(current_frame_data)} - {np.max(current_frame_data)}\n") txt_file.write(f"# 均值: {np.mean(current_frame_data):.2f}, 标准差: {np.std(current_frame_data):.2f}\n") txt_file.write("#\n") # 写入数据 for row in current_frame_data: if format_type == "hex": # 转换为十六进制格式 row_str = ' '.join([f"{int(val):04X}" for val in row]) elif format_type == "signed": # 带符号十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) else: # decimal # 十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) txt_file.write(row_str + '\n') messagebox.showinfo("成功", f"帧数据已保存到 {file_path}") except Exception as e: messagebox.showerror("错误", f"保存文件时出错: {str(e)}") tk.Button(format_window, text="保存", command=save_with_format).pack(pady=10) tk.Button(format_window, text="取消", command=format_window.destroy).pack(pady=10) def set_display_source(self, source): """设置显示源""" self.current_display_source = source if source == "original": # 显示原始数据 if self.current_file_alias and self.current_file_alias in self.files: frames = self.files[self.current_file_alias]['frames'] if frames and self.current_frame < len(frames): current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 # 更新图像显示 self.current_image.set_data(current_frame_data) # 更新显示范围 self.update_display_range() elif source == "operation" and self.selected_operation and self.selected_operation in self.operations: # 显示运算结果 self.show_operation_result(self.selected_operation) def on_source_changed(self, event=None): """数据源选择变化时调用""" source = self.source_var.get() if source == "原始数据": self.set_display_source("original") elif source == "运算结果" and self.operations: if not self.selected_operation or self.selected_operation not in self.operations: # 如果没有选择运算结果,选择第一个 self.selected_operation = list(self.operations.keys())[0] self.set_display_source("operation") def _get_or_create_menu(self, menu_name): """获取或创建菜单""" try: if not self.menubar: self.menubar = tk.Menu(self.root) self.root.config(menu=self.menubar) # 检查菜单是否已存在 for i in range(self.menubar.index('end') + 1): menu_label = self.menubar.entrycget(i, 'label') if menu_label == menu_name: return self.menubar.nametowidget(self.menubar.entrycget(i, 'menu')) # 创建新菜单 new_menu = tk.Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label=menu_name, menu=new_menu) return new_menu except Exception as e: logger.error(f"获取或创建菜单时出错: {e}") # 不返回任何内容,让调用者处理None值 return None def update_display_menu(self): """更新显示菜单""" try: display_menu = self._get_or_create_menu("显示") if not display_menu: return display_menu.delete(0, 'end') # 创建显示模式变量 if not hasattr(self, 'display_mode_var'): self.display_mode_var = tk.StringVar(value=self.display_mode) # 显示模式 display_mode_menu = tk.Menu(display_menu, tearoff=0) display_menu.add_cascade(label="显示模式", menu=display_mode_menu) display_modes = ["固定范围", "帧平均±1000", "帧平均±200", "自定义"] for mode in display_modes: display_mode_menu.add_radiobutton( label=mode, variable=self.display_mode_var, # 修正为变量对象 value=mode, command=lambda m=mode: self.set_display_mode(m) ) file_menu.add_separator() file_menu.add_command(label="导出当前帧为TXT...", command=self.export_to_txt) file_menu.add_separator() file_menu.add_command(label="退出", command=self.root.quit) except Exception as e: logger.error(f"更新文件菜单时出错: {e}") def _update_source_menu(self): """更新数据源菜单""" try: if not self.menubar: return # 查找显示菜单 display_menu = None for i in range(self.menubar.index('end') + 1): if self.menubar.entrycget(i, 'label') == "显示": menu_name = self.menubar.entrycget(i, 'menu') display_menu = self.menubar.nametowidget(menu_name) break if not display_menu: return # 查找数据源菜单项 source_menu = None for i in range(display_menu.index('end') + 1): if display_menu.entrycget(i, 'label') == "数据源": menu_name = display_menu.entrycget(i, 'menu') source_menu = display_menu.nametowidget(menu_name) break if not source_menu: return # 清除并重新添加菜单项 source_menu.delete(0, 'end') source_menu.add_radiobutton( label="原始数据", variable=self.source_var, value="原始数据", command=self.on_source_changed ) if self.operations: source_menu.add_radiobutton( label="运算结果", variable=self.source_var, value="运算结果", command=self.on_source_changed ) except Exception as e: logger.error(f"更新数据源菜单时出错: {e}") def update_formula_menu(self): """更新公式菜单""" try: formula_menu = self._get_or_create_menu("公式") formula_menu.delete(0, 'end') formula_menu.add_command(label="创建公式...", command=self.create_formula) if self.operations: formula_menu.add_separator() operations_menu = tk.Menu(formula_menu, tearoff=0) formula_menu.add_cascade(label="运算结果", menu=operations_menu) for name in sorted(self.operations.keys()): operations_menu.add_command( label=f"{name}: {self.operations[name]['formula']}", command=lambda n=name: self.show_operation_result(n) ) formula_menu.add_separator() formula_menu.add_command(label="保存当前运算结果", command=self.save_operation_result) except Exception as e: logger.error(f"更新公式菜单时出错: {e}") def update_operations_menu(self): """更新运算结果菜单""" try: operations_menu = self._get_or_create_menu("运算结果") operations_menu.delete(0, 'end') if self.operations: for name in sorted(self.operations.keys()): operations_menu.add_command( label=f"{name}: {self.operations[name]['formula']}", command=lambda n=name: self.show_operation_result(n) ) operations_menu.add_separator() operations_menu.add_command( label="保存当前运算结果", command=self.save_operation_result ) except Exception as e: logger.error(f"更新运算结果菜单时出错: {e}") def _update_source_menu(self): """更新数据源菜单""" try: if not self.menubar: return # 查找显示菜单 display_menu_idx = None for i in range(self.menubar.index('end') + 1): if self.menubar.entrycget(i, 'label') == "显示": display_menu_idx = i break if display_menu_idx is None: return display_menu = self.menubar.entrycget(display_menu_idx, 'menu') # 查找数据源菜单项 source_menu_idx = None for i in range(display_menu.index('end') + 1): if display_menu.entrycget(i, 'label') == "数据源": source_menu_idx = i break if source_menu_idx is None: return source_menu = display_menu.entrycget(source_menu_idx, 'menu') # 清除并重新添加菜单项 source_menu.delete(0, 'end') source_menu.add_radiobutton( label="原始数据", variable=self.source_var, value="原始数据", command=self.on_source_changed ) if self.operations: source_menu.add_radiobutton( label="运算结果", variable=self.source_var, value="运算结果", command=self.on_source_changed ) except Exception as e: logger.error(f"更新数据源菜单时出错: {e}") def initialize_ui(self): """初始化用户界面""" self.root = tk.Tk() self.root.title("二进制数据可视化工具") self.root.geometry("1200x800") # 创建主框架 main_frame = tk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True) # 创建控制框架 control_frame = tk.Frame(main_frame, height=50) control_frame.pack(fill=tk.X, side=tk.TOP, padx=10, pady=5) # 数据源切换控件 source_frame = tk.Frame(control_frame) source_frame.pack(fill=tk.X, side=tk.RIGHT, padx=(10, 0), pady=5) tk.Label(source_frame, text="显示源:").pack(side=tk.LEFT, padx=5) self.source_var = tk.StringVar(value="original") source_combo = ttk.Combobox(source_frame, textvariable=self.source_var, width=15) source_combo['values'] = ("原始数据", "运算结果") source_combo.pack(side=tk.LEFT, padx=5) source_combo.bind("<<ComboboxSelected>>", self.on_source_changed) # 创建Matplotlib图形 self.fig, (self.ax_image, self.ax_hist) = plt.subplots(2, 1, figsize=(10, 7), gridspec_kw={'height_ratios': [4, 1]}) self.fig.subplots_adjust(hspace=0.3) # 初始图像 self.current_image = self.ax_image.imshow(np.zeros((self.height, self.width)), cmap='gray') self.ax_image.axis('off') # 初始直方图 self.ax_hist.hist(np.zeros(self.height * self.width), bins=50, alpha=0.7) self.ax_hist.set_title("像素值分布直方图") self.ax_hist.set_xlabel("像素值") self.ax_hist.set_ylabel("频次") self.ax_hist.grid(True) # 创建Canvas self.canvas = FigureCanvasTkAgg(self.fig, master=main_frame) self.canvas.draw() self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 添加Matplotlib工具栏 toolbar = NavigationToolbar2Tk(self.canvas, main_frame) toolbar.update() # 创建帧控制滑块 slider_frame = tk.Frame(main_frame, height=50) slider_frame.pack(fill=tk.X, side=tk.BOTTOM, padx=10, pady=5) # 帧号标签和输入框 tk.Label(slider_frame, text="帧号:").pack(side=tk.LEFT, padx=5) # 创建Matplotlib文本框用于输入帧号 self.textbox_frame = TextBox( plt.axes([0.15, 0.01, 0.05, 0.04]), '', initial="1", color='white', hovercolor='lightgoldenrodyellow' ) self.textbox_frame.on_submit(self.on_frame_number_submit) # 创建Matplotlib滑块用于控制帧 self.slider_frame = Slider( plt.axes([0.25, 0.01, 0.65, 0.04]), '帧', 0, 100, # 初始范围,会在加载文件后更新 valinit=0, valstep=1 ) self.slider_frame.on_changed(self.on_frame_change) self.slider_frame.set_active(False) # 初始禁用,直到加载文件 # 初始化菜单 self.initialize_menus() # 更新菜单状态 self.update_file_menu() self.update_display_menu() self.update_formula_menu() # 设置数据源菜单 self._update_source_menu() # 显示欢迎信息 self.fig.suptitle("欢迎使用二进制数据可视化工具\n请从文件菜单打开二进制数据文件") self.canvas.draw() def initialize_menus(self): """初始化菜单栏""" if not self.menubar: self.menubar = tk.Menu(self.root) self.root.config(menu=self.menubar) def run(self): """运行应用程序""" self.initialize_ui() self.root.mainloop() if __name__ == "__main__": app = BinaryVisualizer() app.run() 提示Traceback (most recent call last): File "E:\zlt_work\zlt_work\DATA\读取多文件测试.py", line 1185, in <module> app.run() File "E:\zlt_work\zlt_work\DATA\读取多文件测试.py", line 1180, in run self.initialize_ui() File "E:\zlt_work\zlt_work\DATA\读取多文件测试.py", line 1161, in initialize_ui self.update_file_menu() AttributeError: 'BinaryVisualizer' object has no attribute 'update_file_menu'
最新发布
06-25
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值