diff --git a/core/main_entry.py b/core/main_entry.py index d4b029b..3422ee3 100644 --- a/core/main_entry.py +++ b/core/main_entry.py @@ -1,82 +1,149 @@ # coding:utf-8 +# MainEntry 负责应用主窗口与菜单的统一构建: +# - 通过 build_menu_model 定义跨平台统一的菜单数据结构 +# - 在 macOS 上使用 Tk 菜单栏;在非 macOS 上使用 pystray 系统托盘 +# - 所有菜单项均绑定到同名处理函数,切换平台无需改动业务逻辑 import tkinter as tk from core.logger import LogLevel, PrintLog -from PIL import Image -import pystray import threading +import sys class MainEntry: def __init__(self, master): + # 初始化 Tk 窗口属性与基础状态 self.master = master self.master.title("Main Board") self.master.geometry("800x600") self.master.configure(bg="#f0f0f0") self.master.resizable(False, False) self.master.protocol("WM_DELETE_WINDOW", self.hide_window) + # QMT 开关状态用于动态更新菜单文案 self.qmt_enabled = False self.icon = None - self.create_systray_icon() + # 非 macOS 使用系统托盘(pystray);macOS 使用原生菜单栏 + self.systray_supported = sys.platform != "darwin" + # 首次进入根据平台构建菜单 + self.create_menu() - def create_systray_icon(self): - # 加载图标文件(需确保路径正确) - image = Image.open("logo.png") - # 定义托盘菜单 - menu = ( - pystray.MenuItem('交易大师', None, enabled=False), - pystray.MenuItem(" - 交易复盘", self.handler), - pystray.MenuItem(" - 市场数据", self.handler), - pystray.MenuItem(" - 快速下单", self.handler), - pystray.MenuItem('策略交易', None, enabled=False), - pystray.MenuItem(" - 交易看板", self.handler), - pystray.MenuItem(" - 策略中心", None), - pystray.MenuItem(" - 策略定制", None), - pystray.MenuItem('实时数据', None, enabled=False), - pystray.MenuItem(" - QMT (已关闭)" if not self.qmt_enabled else " - QMT (已开启)", self.marketDataSwitch), - pystray.Menu.SEPARATOR, - pystray.MenuItem(" - 控制台", self.show_window, default=True), - pystray.MenuItem(" - 设置", self.marketDataSwitch), - pystray.MenuItem(" - 退出", self.quit_window) - ) - - if self.icon: - self.icon.menu = menu - self.icon.update_menu() + def build_menu_model(self): + # 菜单模型统一描述所有菜单: + # - 每个分组包含 label 与 items + # - item 支持:label 文案、action 处理函数名、enabled 启用状态、default 默认项、separator 分隔符 + # - 文案可根据状态动态生成(如 QMT 开关) + qmt_label = "QMT (已开启)" if self.qmt_enabled else "QMT (已关闭)" + return [ + { + "label": "交易大师", + "items": [ + {"label": "交易复盘", "action": "handler", "enabled": True}, + {"label": "市场数据", "action": "handler", "enabled": True}, + {"label": "快速下单", "action": "handler", "enabled": True}, + ], + }, + { + "label": "策略交易", + "items": [ + {"label": "交易看板", "action": "handler", "enabled": True}, + {"label": "策略中心", "action": None, "enabled": False}, + {"label": "策略定制", "action": None, "enabled": False}, + ], + }, + { + "label": "实时数据", + "items": [ + {"label": qmt_label, "action": "marketDataSwitch", "enabled": True}, + ], + }, + { + "label": "系统", + "items": [ + {"label": "控制台", "action": "show_window", "enabled": True, "default": True}, + {"label": "设置", "action": "marketDataSwitch", "enabled": True}, + {"separator": True}, + {"label": "退出", "action": "quit_window", "enabled": True}, + ], + }, + ] + + def create_menu(self): + # 根据统一菜单模型与平台类型,渲染到系统托盘或 Tk 菜单栏 + model = self.build_menu_model() + if self.systray_supported: + # 非 macOS:延迟导入 pystray 与 PIL,避免在 macOS 上引入不兼容依赖 + from PIL import Image + import pystray + image = Image.open("logo.png") + items = [] + for group in model: + # 分组标题作为禁用的头部项 + items.append(pystray.MenuItem(group["label"], None, enabled=False)) + for it in group["items"]: + if it.get("separator"): + items.append(pystray.Menu.SEPARATOR) + else: + fn = getattr(self, it["action"]) if it.get("action") else None + items.append(pystray.MenuItem(it["label"], fn, default=it.get("default", False), enabled=it.get("enabled", True))) + menu = tuple(items) + if self.icon: + # 已存在托盘图标:更新菜单 + self.icon.menu = menu + self.icon.update_menu() + else: + # 首次创建托盘图标并在后台线程运行 + self.icon = pystray.Icon("name", image, "标题", menu) + self.trayThread = threading.Thread(target=self.icon.run, daemon=True) + self.trayThread.start() else: - # 创建托盘图标 - self.icon = pystray.Icon("name", image, "标题", menu) - self.trayThread = threading.Thread(target=self.icon.run, daemon=True) - self.trayThread.start() - # 在后台线程运行托盘 + # macOS:使用 Tk 菜单栏 + menu_bar = tk.Menu(self.master) + for group in model: + m = tk.Menu(menu_bar, tearoff=0) + for it in group["items"]: + if it.get("separator"): + m.add_separator() + else: + fn = getattr(self, it["action"]) if it.get("action") else None + if it.get("enabled", True) and fn: + m.add_command(label=it["label"], command=fn) + else: + m.add_command(label=it["label"], state="disabled") + menu_bar.add_cascade(label=group["label"], menu=m) + self.master.config(menu=menu_bar) def marketDataSwitch(self): + # 切换 QMT 开关,并触发菜单重建以更新文案 if self.qmt_enabled: self.qmt_enabled = False PrintLog(LogLevel.INFO, "QMT 市场数据已关闭") else: self.qmt_enabled = True PrintLog(LogLevel.INFO, "QMT 市场数据已开启") - self.create_systray_icon() + self.create_menu() def handler(self): + # 通用占位处理:当前仅记录点击行为,后续可替换为具体业务逻辑 PrintLog(LogLevel.INFO, f"点击了") def hide_window(self): + # 关闭窗口事件:隐藏但不退出应用 PrintLog(LogLevel.INFO, "隐藏主窗口") self.master.withdraw() # 隐藏主窗口 def show_window(self): - self.icon.visible = True + # 显示主窗口;在非 macOS 平台同步让托盘图标可见 + if self.icon: + self.icon.visible = True PrintLog(LogLevel.INFO, "显示主窗口") self.master.deiconify() # 显示主窗口 - def quit_window(self, icon: pystray.Icon): - icon.stop() + def quit_window(self, icon=None): + # 退出应用;在非 macOS 平台时关闭托盘图标 + if icon: + icon.stop() PrintLog(LogLevel.INFO, "退出应用") self.master.quit() self.master.destroy() def run(self): + # 主事件循环入口 self.master.mainloop() - - -