data_frame = data_frame.set_index(‘Name‘)报错:KeyError: ‘Name‘

本文解决在使用pandas DataFrame时,尝试将行名设为'Name'列但出现KeyError的问题。通过检查列名实际包含多余空格,并提供两种解决方案:直接使用列名设置索引或修改列名去除空格。

1.问题

想要去掉index列用行名称作为第一列,第一列名称为'Name',如下图:

输入:data_frame = data_frame.set_index('Name'),报错:KeyError: 'Name'。

2.原因

输入:data_frame.columns[0]查看第一列列名称。显示:

也就是说,列名称不是'Name',而是后面多了许多空格的'Name           

 '

3.解决方法

方法(1):将data_frame = data_frame.set_index('Name')改为data_frame = data_frame.set_index(data_frame.columns[0])。

方法(2):修改列名称,删除Name后面多余的空格,即:

data_frame = data_frame.rename(columns={data_frame.columns[0]: data_frame.columns[0].strip()})

data_frame = data_frame.set_index('Name')

4.最后,成功去除index列:

修改::未解析的引用 ‘TrendAnalysisModule’,,未解析的引用 ‘NumberGenerationModule’,,未解析的引用 ‘ModuleFactory’,,方法必须有第一个形参,通常叫做 ‘self’,,未解析的引用 ‘MainWindow’,,,从外部作用域隐藏名称 ‘self’,,方法 ‘on_result’ 可能为 ‘static’,,访问类的 protected 成员 _handle_module_run,,方法 ‘_format_number’ 可能为 ‘static’,,方法 ‘main’ 可能为 ‘static’,,,用户要求:吧子类界面(1.输入分析)标签后面的条目宽度调整要尽量宽,,在号码池里,标签后面的条目的宽度也是要尽量宽,按钮放在底部靠右的位置,,,代码【import sys import os import json import random import time import threading import logging import uuid from enum import Enum from typing import Any, Dict, List, Optional, Callable, Union, Literal, Tuple from collections import defaultdict import tkinter as tk from tkinter import ( Tk, Frame, Label, Button, Entry, StringVar, PanedWindow, Canvas, Scrollbar, messagebox, Text, LabelFrame, Toplevel, Listbox ) from tkinter.constants import VERTICAL, RAISED, BOTH, X, Y, LEFT, RIGHT, TOP, BOTTOM, HORIZONTAL from tkinter import scrolledtext from tkinter import ttk from pathlib import Path 确保项目根目录在 sys.path 中 project_root = Path(file).parent.parent if str(project_root) not in sys.path: sys.path.insert(0, str(project_root)) 尝试导入核心模块,如果失败则创建占位符 try: from core.ui_config import get_ui_config, init_ui_config, UI_CONFIG from core.event_center import event_center from core.event_types import EventType from core.global_config import GlobalConfig except ImportError as import_error: print(f"无法导入核心模块: {import_error}") # -------------- 1. 先放 EventCenter 定义 -------------- class EventCenter: def __init__(self): from collections import defaultdict self._subscribers = defaultdict(list) def subscribe(self, event_type, handler): # 修复:检查event_type是否为字符串 t = event_type.value if hasattr(event_type, 'value') else event_type self._subscribers[t].append(handler) def publish(self, event): # 修复:检查event.type是否为字符串 if isinstance(event, dict): t = event['type'].value if hasattr(event['type'], 'value') else event['type'] else: t = event.type.value if hasattr(event.type, 'value') else event.type for h in self._subscribers.get(t, []): try: h(event) except Exception as exc: logging.error(f"事件处理失败: {exc}") # -------------- 2. 立即实例化 -------------- event_center = EventCenter() # -------------- 3. 其余占位符不动 -------------- UI_CONFIG = type('UI_CONFIG', (), {})() GlobalConfig = type('GlobalConfig', (), {})() def get_ui_config(): return {} def init_ui_config(): pass # 定义事件类型枚举 class EventType(Enum): START_COMMAND = "start_command" DIALOG_OPEN = "dialog_open" MODULE_RUN = "module_run" MODULE_READY = "module_ready" MODULE_COMPLETE = "module_complete" MODULE_RESULT = "module_result" DATA_SUBMIT = "data_submit" UI_UPDATE = "ui_update" ERROR = "error" LOG_EVENT = "log_event" DATA_FREEZE = "data_freeze" MODULE_SWITCH = "module_switch" 配置日志 logging.basicConfig( level=logging.INFO, format=‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’, datefmt=‘%Y-%m-%d %H:%M:%S’ ) logger = logging.getLogger(‘DLT_Analysis’) ====== 全局配置类 ====== class AppConfig: VERSION = “大乐透智能分析平台 v6.0” PROJECT_PATH = r"C:\Users\Administrator\Desktop\project" MODULES_PATH = os.path.join(PROJECT_PATH, “modules”) JSON_PATH = os.path.join(PROJECT_PATH, “json”) LOG_PATH = os.path.join(PROJECT_PATH, “log”) POOL_SAVE_PATH = os.path.join(MODULES_PATH, “号码池.json”) CORE_SAVE_PATH = os.path.join(MODULES_PATH, “核心区数据.json”) DATA_PATH = r’F:\超级\历史数据\历史数据.csv’ MODULE1_SAVE_PATH = os.path.join(JSON_PATH, “module1_save.json”) # 确保目录存在 os.makedirs(JSON_PATH, exist_ok=True) os.makedirs(MODULES_PATH, exist_ok=True) # 模块ID定义 MODULE_IDS = { 'input_analysis': '1.输入分析', 'combination_analysis': '2.组合分析', 'follow_analysis': '3.跟随分析', 'trend_analysis': '4.趋势分析', 'number_generation': '5.数字生成' } # 核心区标签 CORE_LABELS = [ "前区:", "后区:", "前区热号:", "前区冷号:", "后区热号:", "后区冷号:" ] # 号码池标签 POOL_LABELS = [ "前区:", "后区:", "前数字频:", "前数字缺:", "后数字频:", "后数字缺:", "前频繁推:", "后低频推:", "生组合数:", "未组合码:", "前推荐多:", "前推荐少:", "后推荐多:", "后推荐少:", "和值:", "质合比:", "奇偶比:", "断区推荐:", "连号推荐:", "冷热推荐:", "后区热号:", "后区冷号:", "趋势号:" ] # 各模块动态区标签 MODULE_LABELS = { 'input_analysis': ["排除号码:", "前区:", "后区:", "推荐号码:", "前区:", "后区:"], 'combination_analysis': ["前数字频:", "前数字缺:", "后数字频:", "后数字缺:", "前频繁推:", "后低频推:", "生组合数:", "未组合码:"], 'follow_analysis': ["前推荐多:", "前推荐少:", "后推荐多:", "后推荐少:"], 'trend_analysis': ["和值:", "质合比:", "奇偶比:", "断区推荐:", "连号推荐:", "冷热推荐:", "后区热号:", "后区冷号:", "趋势号:"], 'number_generation': ["胆码:", "前区:", "后区:", "生成号码:", "1", "", "2", "", "3", "", "4", "", "5", ""] } # 添加缺失的常量 MODULES = ['input_analysis', 'combination_analysis', 'follow_analysis', 'trend_analysis', 'number_generation'] HISTORY_DATA_PATH = r'F:\超级\历史数据\历史数据.csv' # 事件类型定义 EVENT_TYPES = { 'MODULE_RUN': 'module_run', 'MODULE_RESULT': 'module_result', 'DATA_UPDATE': 'data_update' } ====== Token 管理类 ====== class TokenManager: _instance = None _lock = threading.Lock() def __new__(cls): if cls._instance is None: with cls._lock: if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): if not hasattr(self, '_initialized'): self._current_token = str(uuid.uuid4()) self._token_callbacks = [] self._initialized = True def get_token(self): return self._current_token def refresh_token(self): self._current_token = str(uuid.uuid4()) for callback in self._token_callbacks: callback(self._current_token) return self._current_token def register_token_callback(self, callback): self._token_callbacks.append(callback) 事件数据类 class Event: def init(self, event_id: int, event_type: Union[EventType, str], source: str, target: str, data: dict = None): self.event_id = event_id self.type = event_type self.source = source self.target = target self.data = data or {} self.token = TokenManager().get_token() def __getitem__(self, key): """允许像字典一样访问事件属性""" if key == 'type': return self.type elif key == 'source': return self.source elif key == 'target': return self.target elif key == 'data': return self.data elif key == 'token': return self.token raise KeyError(f"Event has no key '{key}'") ====== 号码池类 ====== class NumberPool: def init(self): self.data_store = { ‘front_hot’: [], ‘back_hot’: [], ‘front_freq’: {}, ‘back_freq’: {}, ‘front_missing’: [], ‘back_missing’: [], ‘recommendations’: {}, ‘generated_numbers’: [], ‘frozen’: False, ‘front_numbers’: [], ‘back_numbers’: [], ‘special_data: {}, ‘organized_data: None, ‘core_data: {} } self.lock = threading.Lock() self.current_module = None self.module_status = {module: False for module in AppConfig.MODULES} self.running = True self._setup_subscriptions() self._load_data() def _setup_subscriptions(self): """设置事件订阅""" # 修复:使用正确的event_type格式 event_center.subscribe( EventType.DATA_SUBMIT.value if hasattr(EventType.DATA_SUBMIT, 'value') else EventType.DATA_SUBMIT, self._handle_data_submit) event_center.subscribe( EventType.DATA_FREEZE.value if hasattr(EventType.DATA_FREEZE, 'value') else EventType.DATA_FREEZE, self._handle_data_freeze) def _handle_data_submit(self, event): """处理数据提交事件""" if self.data_store['frozen']: return with self.lock: for key, value in event.data.items(): if key in self.data_store: if isinstance(value, list): self.data_store[key] = ','.join(map(str, value)) else: self.data_store[key] = str(value) self._save_data() def _handle_data_freeze(self, event): """处理数据冻结事件""" with self.lock: self.data_store['frozen'] = event.data.get('freeze', False) self._save_data() def _save_data(self): """保存数据到文件""" try: with open(AppConfig.POOL_SAVE_PATH, 'w', encoding='utf-8') as f: json.dump(self.data_store, f, ensure_ascii=False, indent=2) except IOError as e: logging.error(f"[号码池] 保存数据失败: {str(e)}") def _load_data(self): """从文件加载数据""" if os.path.exists(AppConfig.POOL_SAVE_PATH): try: with open(AppConfig.POOL_SAVE_PATH, 'r', encoding='utf-8') as f: data = json.load(f) with self.lock: self.data_store.update(data) except (IOError, json.JSONDecodeError) as e: logging.error(f"[号码池] 加载数据失败: {str(e)}") def organize_data(self): """整理号码池数据到核心区""" if self.data_store['frozen']: return False with self.lock: # 数据整理逻辑 core_mapping = { '前区': '前区', '后区': '后区', '前区热号': '前频繁推', '前区冷号': '前推荐少', '后区热号': '后区热号', '后区冷号': '后区冷号' } for core_key, pool_key in core_mapping.items(): if pool_key in self.data_store: self.data_store['core_data'][core_key] = self.data_store[pool_key] # 保存核心区数据 try: with open(AppConfig.CORE_SAVE_PATH, 'w', encoding='utf-8') as f: json.dump(self.data_store['core_data'], f, ensure_ascii=False, indent=2) except IOError as e: logging.error(f"[核心区] 保存数据失败: {str(e)}") self._save_data() return True def get_data(self, key=None): """获取数据""" with self.lock: if key: return self.data_store.get(key) return self.data_store.copy() ====== 基础模块类 ====== class BaseModule: def init(self, module_id: str): self.module_id = module_id self.module_name = AppConfig.MODULE_IDS[module_id] self.dynamic_data = {} self.token_manager = TokenManager() self._initialize() def _initialize(self): """初始化模块""" # 订阅 MODULE_RUN 事件,并检查目标是否为当前模块 # 修复:使用正确的event_type格式 event_center.subscribe( EventType.MODULE_RUN.value if hasattr(EventType.MODULE_RUN, 'value') else EventType.MODULE_RUN, self._handle_module_run) ready_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_READY.value if hasattr(EventType.MODULE_READY, 'value') else EventType.MODULE_READY, source=self.module_id, target='main_ui' ) event_center.publish(ready_event) def _handle_module_run(self, event): """处理模块运行事件""" # 检查事件目标是否为当前模块 if event.target == self.module_id and event.data.get('token') == self.get_token(): self.run_analysis(event.data) def _submit_data(self, data): """提交数据到号码池""" submit_event = Event( event_id=int(time.time()), event_type=EventType.DATA_SUBMIT.value if hasattr(EventType.DATA_SUBMIT, 'value') else EventType.DATA_SUBMIT, source=self.module_id, target='pool', data=data ) event_center.publish(submit_event) def run_analysis(self, input_data=None): """运行分析(子类重写)""" pass def get_dynamic_labels(self): """获取动态区标签""" return AppConfig.MODULE_LABELS.get(self.module_id, []) def get_token(self): """获取当前token""" return self.token_manager.get_token() ====== 输入分析模块 ====== class InputAnalysisModule(BaseModule): def init(self, module_id: str): super().init(module_id) self.dynamic_data = { “排除号码”: { “前区”: [“” for _ in range(10)], “后区”: [“” for _ in range(10)] }, “推荐号码”: { “前区”: [], “后区”: [] } } def run_analysis(self, input_data=None): """运行输入分析""" try: exclude_front = input_data.get('exclude_front', []) exclude_back = input_data.get('exclude_back', []) all_front = list(range(1, 36)) recommend_front = [num for num in all_front if num not in exclude_front][:10] all_back = list(range(1, 13)) recommend_back = [num for num in all_back if num not in exclude_back][:4] result_data = { "排除号码": { "前区": ["" for _ in range(10)], "后区": ["" for _ in range(10)] }, "推荐号码": { "前区": recommend_front, "后区": recommend_back } } self.dynamic_data.update(result_data) self._submit_data(result_data) # 发布结果事件 result_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_RESULT.value if hasattr(EventType.MODULE_RESULT, 'value') else EventType.MODULE_RESULT, source=self.module_id, target='main_ui', data={ 'module_id': self.module_id, 'token': self.get_token(), 'data': result_data } ) event_center.publish(result_event) return True, "分析完成" except Exception as e: return False, f"分析失败: {str(e)}" ====== 组合分析模块 ====== class CombinationAnalysisModule(BaseModule): def init(self, module_id: str): super().init(module_id) self.dynamic_data = { “前数字频”: {}, “前数字缺”: [], “后数字频”: {}, “后数字缺”: [], “前频繁推”: [], “后低频推”: [], “生组合数”: 0, “未组合码”: [] } def run_analysis(self, input_data=None): """运行组合分析""" try: result_data = { "前数字频": {i: random.randint(1, 100) for i in range(1, 36)}, "前数字缺": random.sample(range(1, 36), 5), "后数字频": {i: random.randint(1, 50) for i in range(1, 13)}, "后数字缺": random.sample(range(1, 13), 2), "前频繁推": sorted(random.sample(range(1, 36), 5)), "后低频推": sorted(random.sample(range(1, 13), 2)), "生组合数": random.randint(100, 1000), "未组合码": random.sample(range(1, 36), 5) } self.dynamic_data.update(result_data) self._submit_data(result_data) # 发布结果事件 result_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_RESULT.value if hasattr(EventType.MODULE_RESULT, 'value') else EventType.MODULE_RESULT, source=self.module_id, target='main_ui', data={ 'module_id': self.module_id, 'token': self.get_token(), 'data': result_data } ) event_center.publish(result_event) return True, "分析完成" except Exception as e: return False, f"分析失败: {str(e)}" ====== 跟随分析模块 ====== class FollowAnalysisModule(BaseModule): def init(self, module_id: str): super().init(module_id) self.dynamic_data = { “前推荐多”: [], “前推荐少”: [], “后推荐多”: [], “后推荐少”: [] } def run_analysis(self, input_data=None): """运行跟随分析""" try: result_data = { "前推荐多": sorted(random.sample(range(1, 36), 5)), "前推荐少": sorted(random.sample(range(1, 36), 5)), "后推荐多": sorted(random.sample(range(1, 13), 3)), "后推荐少": sorted(random.sample(range(1, 13), 3)) } self.dynamic_data.update(result_data) self._submit_data(result_data) # 发布结果事件 result_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_RESULT.value if hasattr(EventType.MODULE_RESULT, 'value') else EventType.MODULE_RESULT, source=self.module_id, target='main_ui', data={ 'module_id': self.module_id, 'token': self.get_token(), 'data': result_data } ) event_center.publish(result_event) return True, "分析完成" except Exception as e: return False, f"分析失败: {str(e)}" # ====== 趋势分析模块 ====== class TrendAnalysisModule(BaseModule): def __init__(self, module_id: str): super().__init__(module_id) self.dynamic_data = { "和值": "", "质合比": "", "奇偶比": "", "断区推荐": "", "连号推荐": "", "冷热推荐": "", "后区热号": "", "后区冷号": "", "趋势号": "" } def run_analysis(self, input_data=None): """运行趋势分析""" try: result_data = { "和值": f"{random.randint(60, 80)}-{random.randint(100, 125)}", "质合比": f"{random.randint(1, 3)}:{random.randint(2, 4)}", "奇偶比": f"{random.randint(2, 4)}:{random.randint(1, 3)}", "断区推荐": random.choice(["一区", "二区", "三区", "四区", "五区,七区"]), "连号推荐": f"{random.randint(1, 10)}-{random.randint(11, 20)}", "冷热推荐": f"热号:{','.join(map(str, sorted(random.sample(range(1, 36), 5))))}", "后区热号": ','.join(map(str, sorted(random.sample(range(1, 13), 3)))), "后区冷号": ','.join(map(str, sorted(random.sample(range(1, 13), 2)))), "趋势号": ','.join(map(str, sorted(random.sample(range(1, 36), 8)))) } self.dynamic_data.update(result_data) self._submit_data(result_data) # 发布结果事件 result_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_RESULT.value if hasattr(EventType.MODULE_RESULT, 'value') else EventType.MODULE_RESULT, source=self.module_id, target='main_ui', data={ 'module_id': self.module_id, 'token': self.get_token(), 'data': result_data } ) event_center.publish(result_event) return True, "分析完成" except Exception as e: return False, f"分析失败: {str(e)}" # ====== 号码生成模块 ====== class NumberGenerationModule(BaseModule): def __init__(self, module_id: str): super().__init__(module_id) self.dynamic_data = { "胆码": { "前区": ["" for _ in range(5)], "后区": ["" for _ in range(5)] }, "推荐5注号码:": { "1.": "", "2.": "", "3.": "", "4.": "", "5.": "" } } def run_analysis(self, input_data=None): """运行号码生成""" try: front_dan = input_data.get('front_dan', []) back_dan = input_data.get('back_dan', []) generated_numbers = [] for _ in range(5): # 前区处理 if front_dan: dan_count = random.randint(1, min(2, len(front_dan))) selected_dan = random.sample(front_dan, dan_count) remaining_front = [num for num in range(1, 36) if num not in selected_dan] other_front = random.sample(remaining_front, 5 - dan_count) front_numbers = sorted(selected_dan + other_front) else: front_numbers = sorted(random.sample(range(1, 36), 5)) # 后区处理 if back_dan: dan_count = min(1, len(back_dan)) selected_dan = random.sample(back_dan, dan_count) remaining_back = [num for num in range(1, 13) if num not in selected_dan] other_back = random.sample(remaining_back, 2 - dan_count) back_numbers = sorted(selected_dan + other_back) else: back_numbers = sorted(random.sample(range(1, 13), 2)) generated_numbers.append(f"前区: {front_numbers} 后区: {back_numbers}") result_data = { "胆码": { "前区": front_dan, "后区": back_dan }, "生成号码": generated_numbers } self.dynamic_data.update(result_data) self._submit_data({"生成号码": generated_numbers}) # 发布结果事件 result_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_RESULT.value if hasattr(EventType.MODULE_RESULT, 'value') else EventType.MODULE_RESULT, source=self.module_id, target='main_ui', data={ 'module_id': self.module_id, 'token': self.get_token(), 'data': result_data } ) event_center.publish(result_event) return True, "号码生成完成" except Exception as e: return False, f"号码生成失败: {str(e)}" # ====== 模块工厂类 ====== class ModuleFactory: @staticmethod def create_module(module_id: str) -> BaseModule: if module_id == 'input_analysis': return InputAnalysisModule(module_id) elif module_id == 'combination_analysis': return CombinationAnalysisModule(module_id) elif module_id == 'follow_analysis': return FollowAnalysisModule(module_id) elif module_id == 'trend_analysis': return TrendAnalysisModule(module_id) elif module_id == 'number_generation': return NumberGenerationModule(module_id) else: raise ValueError(f"未知模块ID: {module_id}") # ====== 主窗口类 ====== class MainWindow: def __init__(self, root, number_pool): self.number_pool = number_pool self.root = root self.root.title(AppConfig.VERSION) self.root.geometry("1400x800") self.token_manager = TokenManager() self._current_token = self.token_manager.get_token() self.token_var = StringVar(value=self._current_token) # 初始化所有属性 self.current_module = None self.modules = {} self.period_var = None self.core_vars = {} self.dynamic_content = None self.dynamic_button_frame = None self.run_button = None self.clear_button = None self.save_button = None self.refresh_button = None self.dynamic_vars = {} self.pool_vars = {} self.module_buttons = {} self.exclude_front_entries = [] # 改为存储Entry控件 self.exclude_back_entries = [] # 改为存储Entry控件 self.var_refront = None self.var_reback = None # 使用全局事件中心 self.event_center = event_center # 注册事件订阅 self._setup_event_subscriptions() # 创建主框架 self.main_frame = Frame(root) self.main_frame.pack(fill='both', expand=True, padx=10, pady=10) # 创建三栏布局 self.create_left_panel() self.create_center_panel() self.create_right_panel() # 状态栏 self.status_var = StringVar(value="就绪") self.status_bar = Label(root, textvariable=self.status_var, bd=1, relief=RAISED, anchor='w') self.status_bar.pack(side='bottom', fill='x') # 注册token更新回调 self.token_manager.register_token_callback(self._on_token_update) logger.info("主窗口初始化完成") def _on_token_update(self, new_token): """Token更新回调""" self._current_token = new_token self.token_var.set(new_token) logger.info(f"Token已更新: {new_token}") def on_result(self, event): """处理结果事件 - 静态方法""" print(f'[MainWindow] 收到回执 token={event["token"]}') # 后续 emit 信号或更新 UI 写这里 def _setup_event_subscriptions(self): """设置事件订阅""" # 修复:使用正确的event_type格式 event_center.subscribe( EventType.MODULE_RESULT.value if hasattr(EventType.MODULE_RESULT, 'value') else EventType.MODULE_RESULT, self._handle_module_result) event_center.subscribe(EventType.ERROR.value if hasattr(EventType.ERROR, 'value') else EventType.ERROR, self._handle_module_error) # 添加MODULE_RUN事件订阅 event_center.subscribe( EventType.MODULE_RUN.value if hasattr(EventType.MODULE_RUN, 'value') else EventType.MODULE_RUN, self._handle_module_run) def _handle_module_run(self, event): """处理模块运行事件 - 转发到对应模块""" if event.target in self.modules: module = self.modules[event.target] if hasattr(module, '_handle_module_run'): module._handle_module_run(event) def create_left_panel(self): """创建左侧模块按钮面板""" left_frame = LabelFrame(self.main_frame, text="功能模块", width=200) left_frame.pack(side='left', fill='y', padx=(0, 10)) left_frame.pack_propagate(False) # 创建按钮面板 button_frame = Frame(left_frame) button_frame.pack(fill='both', expand=True, padx=5, pady=5) # 添加模块按钮 for module_id, module_name in AppConfig.MODULE_IDS.items(): btn = Button(button_frame, text=module_name, command=lambda mid=module_id: self.select_module(mid), height=2, font=("Arial", 11)) btn.pack(fill='x', pady=5) self.module_buttons[module_id] = btn def select_module(self, module_id): """选择模块""" self.current_module = module_id self.update_dynamic_area(module_id) self.status_var.set(f"已选择模块: {AppConfig.MODULE_IDS[module_id]}") def create_center_panel(self): """创建中间面板""" center_frame = Frame(self.main_frame) center_frame.pack(side='left', fill='both', expand=True, padx=(0, 10)) center_frame.config(width=500) # 期号区 period_frame = LabelFrame(center_frame, text="期号信息", height=60) period_frame.pack(fill='x', pady=(0, 10)) period_frame.pack_propagate(False) # 获取最新期号 latest_period = self.get_latest_period() self.period_var = StringVar(value=f"当前期号: {latest_period}") Label(period_frame, textvariable=self.period_var, font=("Arial", 12)).pack(pady=10) # Token显示区域 token_frame = LabelFrame(center_frame, text="Token管理", height=50) token_frame.pack(fill='x', pady=(0, 10)) token_frame.pack_propagate(False) token_content = Frame(token_frame) token_content.pack(fill='x', padx=10, pady=5) Label(token_content, text="当前Token:").pack(side=tk.LEFT) token_label = Label(token_content, textvariable=self.token_var, relief='sunken', width=25) token_label.pack(side=tk.LEFT, padx=5) # Token按钮靠右对齐 token_button_frame = Frame(token_content) token_button_frame.pack(side=tk.RIGHT) refresh_token_btn = Button(token_button_frame, text="刷新Token", command=self.refresh_token, width=10) refresh_token_btn.pack(side=tk.RIGHT, padx=2) # 核心区 core_frame = LabelFrame(center_frame, text="核心区") core_frame.pack(fill='x', pady=(0, 10)) self.core_vars = {} for label in AppConfig.CORE_LABELS: frame = Frame(core_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') var = StringVar() self.core_vars[label.strip(':')] = var Label(frame, textvariable=var, relief='sunken', width=15).pack(side='left', fill='x', expand=True) # 动态区 dynamic_frame = LabelFrame(center_frame, text="动态区") dynamic_frame.pack(fill='both', expand=True) # 动态区内容框架 self.dynamic_content = Frame(dynamic_frame) self.dynamic_content.pack(fill='both', expand=True, padx=5, pady=5) # 动态区底部按钮 self.dynamic_button_frame = Frame(dynamic_frame) self.dynamic_button_frame.pack(fill='x', pady=5) # 使用Frame来让按钮靠右 button_right_frame = Frame(self.dynamic_button_frame) button_right_frame.pack(side=tk.RIGHT) self.run_button = Button(button_right_frame, text="运行", command=self.run_module, width=8) self.run_button.pack(side=tk.LEFT, padx=5) self.clear_button = Button(button_right_frame, text="清除", command=self.clear_dynamic, width=8) self.clear_button.pack(side=tk.LEFT, padx=5) self.save_button = Button(button_right_frame, text="保存", command=self.save_dynamic_data, width=8) self.save_button.pack(side=tk.LEFT, padx=5) self.refresh_button = Button(button_right_frame, text="刷新", command=self.refresh_pool, width=8) self.refresh_button.pack(side=tk.LEFT, padx=5) # 初始化动态区 self.dynamic_vars = {} @staticmethod def get_latest_period(): """从历史数据文件中获取最新期号""" try: if os.path.exists(AppConfig.HISTORY_DATA_PATH): encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1'] for encoding in encodings: try: with open(AppConfig.HISTORY_DATA_PATH, 'r', encoding=encoding) as f: lines = f.readlines() if lines: for i in range(len(lines) - 1, -1, -1): line = lines[i].strip() if line and line.split(',')[0].isdigit(): return line.split(',')[0] break except UnicodeDecodeError: continue return "2025001" except Exception as e: logger.error(f"获取最新期号失败: {str(e)}") return "2025001" def refresh_token(self): """刷新Token""" new_token = self.token_manager.refresh_token() self.token_var.set(new_token) self.status_var.set("Token已刷新") def clear_dynamic(self): """清除动态区数据""" for var in self.dynamic_vars.values(): if isinstance(var, StringVar): var.set('') elif isinstance(var, Text): var.delete(1.0, 'end') self.status_var.set("动态区已清除") def save_dynamic_data(self): """保存动态区数据""" try: module_id = self.current_module if not module_id: messagebox.showwarning("警告", "请先选择一个模块") return data_to_save = {} for key, var in self.dynamic_vars.items(): if isinstance(var, StringVar): data_to_save[key] = var.get() elif isinstance(var, Text): data_to_save[key] = var.get(1.0, 'end').strip() save_path = os.path.join(AppConfig.JSON_PATH, f"{module_id}_save.json") with open(save_path, 'w', encoding='utf-8') as f: json.dump(data_to_save, f, ensure_ascii=False, indent=2) self.status_var.set(f"数据已保存到 {save_path}") messagebox.showinfo("成功", f"数据已保存到 {save_path}") except Exception as e: messagebox.showerror("错误", f"保存失败: {str(e)}") def refresh_pool(self): """刷新号码池""" try: pool_data = self.number_pool.get_data() for label in AppConfig.POOL_LABELS: key = label.strip(':') if key in pool_data: value = pool_data[key] if isinstance(value, list): self.pool_vars[label].set(','.join(map(str, value))) else: self.pool_vars[label].set(str(value)) self.status_var.set("号码池已刷新") except Exception as e: messagebox.showerror("错误", f"刷新失败: {str(e)}") def update_dynamic_area(self, module_id): """更新动态区显示""" # 清除现有内容 for widget in self.dynamic_content.winfo_children(): widget.destroy() self.dynamic_vars = {} # 创建模块实例(如果不存在) if module_id not in self.modules: self.modules[module_id] = ModuleFactory.create_module(module_id) module = self.modules[module_id] labels = module.get_dynamic_labels() # 根据模块ID创建不同的UI if module_id == 'input_analysis': self._create_input_analysis_ui(labels) elif module_id == 'number_generation': self._create_number_generation_ui(labels) else: self._create_default_dynamic_ui(labels) def _create_input_analysis_ui(self, labels): """创建输入分析模块的UI""" # 排除号码区域 exclude_frame = LabelFrame(self.dynamic_content, text="排除号码") exclude_frame.pack(fill='x', pady=5) # 前区排除 front_frame = Frame(exclude_frame) front_frame.pack(fill='x', pady=2) Label(front_frame, text="前区:", width=8, anchor='w').pack(side='left') self.exclude_front_entries = [] for i in range(10): var = StringVar() entry = Entry(front_frame, textvariable=var, width=4) entry.pack(side='left', padx=2) self.exclude_front_entries.append(entry) self.dynamic_vars[f"exclude_front_{i}"] = var # 后区排除 back_frame = Frame(exclude_frame) back_frame.pack(fill='x', pady=2) Label(back_frame, text="后区:", width=8, anchor='w').pack(side='left') self.exclude_back_entries = [] for i in range(10): var = StringVar() entry = Entry(back_frame, textvariable=var, width=4) entry.pack(side='left', padx=2) self.exclude_back_entries.append(entry) self.dynamic_vars[f"exclude_back_{i}"] = var # 推荐号码区域 recommend_frame = LabelFrame(self.dynamic_content, text="推荐号码") recommend_frame.pack(fill='x', pady=5) # 前区推荐 front_rec_frame = Frame(recommend_frame) front_rec_frame.pack(fill='x', pady=2) Label(front_rec_frame, text="前区:", width=8, anchor='w').pack(side='left') self.var_refront = StringVar() Label(front_rec_frame, textvariable=self.var_refront, relief='sunken', width=30).pack(side='left', fill='x', expand=True) # 后区推荐 back_rec_frame = Frame(recommend_frame) back_rec_frame.pack(fill='x', pady=2) Label(back_rec_frame, text="后区:", width=8, anchor='w').pack(side='left') self.var_reback = StringVar() Label(back_rec_frame, textvariable=self.var_reback, relief='sunken', width=30).pack(side='left', fill='x', expand=True) def _create_number_generation_ui(self, labels): """创建号码生成模块的UI""" # 胆码区域 dan_frame = LabelFrame(self.dynamic_content, text="胆码") dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x', pady=2) Label(front_dan_frame, text="前区:", width=8, anchor='w').pack(side='left') front_dan_entries = [] for i in range(5): var = StringVar() entry = Entry(front_dan_frame, textvariable=var, width=4) entry.pack(side='left', padx=2) front_dan_entries.append(entry) self.dynamic_vars[f"front_dan_{i}"] = var # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x', pady=2) Label(back_dan_frame, text="后区:", width=8, anchor='w').pack(side='left') back_dan_entries = [] for i in range(5): var = StringVar() entry = Entry(back_dan_frame, textvariable=var, width=4) entry.pack(side='left', padx=2) back_dan_entries.append(entry) self.dynamic_vars[f"back_dan_{i}"] = var # 生成号码区域 gen_frame = LabelFrame(self.dynamic_content, text="生成号码") gen_frame.pack(fill='both', expand=True, pady=5) # 使用文本框显示生成的号码 gen_text = scrolledtext.ScrolledText(gen_frame, height=10, width=50) gen_text.pack(fill='both', expand=True, padx=5, pady=5) self.dynamic_vars["generated_numbers"] = gen_text def _create_default_dynamic_ui(self, labels): """创建默认的动态区UI""" for label in labels: frame = Frame(self.dynamic_content) frame.pack(fill='x', pady=2) Label(frame, text=label, width=15, anchor='w').pack(side='left') var = StringVar() entry = Entry(frame, textvariable=var, width=30) entry.pack(side='left', fill='x', expand=True) self.dynamic_vars[label] = var def create_right_panel(self): """创建右侧号码池面板""" right_frame = LabelFrame(self.main_frame, text="号码池", width=300) right_frame.pack(side='right', fill='y', padx=(10, 0)) right_frame.pack_propagate(False) # 创建滚动框架 canvas = Canvas(right_frame) scrollbar = Scrollbar(right_frame, orient='vertical', command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 号码池标签 self.pool_vars = {} for label in AppConfig.POOL_LABELS: frame = Frame(scrollable_frame) frame.pack(fill='x', pady=2, padx=5) Label(frame, text=label, width=12, anchor='w').pack(side='left') var = StringVar() self.pool_vars[label] = var Label(frame, textvariable=var, relief='sunken', width=15).pack(side='left', fill='x', expand=True) # 添加整理按钮 organize_btn = Button(scrollable_frame, text="整理到核心区", command=self.organize_to_core, height=2) organize_btn.pack(fill='x', pady=10, padx=5) def organize_to_core(self): """整理号码池数据到核心区""" try: if self.number_pool.organize_data(): # 更新核心区显示 core_data = self.number_pool.get_data('core_data') or {} for label in AppConfig.CORE_LABELS: key = label.strip(':') if key in core_data: value = core_data[key] if isinstance(value, list): self.core_vars[key].set(','.join(map(str, value))) else: self.core_vars[key].set(str(value)) self.status_var.set("数据已整理到核心区") else: self.status_var.set("数据已冻结,无法整理") except Exception as e: messagebox.showerror("错误", f"整理失败: {str(e)}") def run_module(self): """运行当前模块""" if not self.current_module: messagebox.showwarning("警告", "请先选择一个模块") return try: # 获取输入数据 input_data = {} for key, var in self.dynamic_vars.items(): if key.startswith('exclude_front_'): if key not in input_data: input_data['exclude_front'] = [] value = var.get().strip() if value and value.isdigit(): input_data['exclude_front'].append(int(value)) elif key.startswith('exclude_back_'): if key not in input_data: input_data['exclude_back'] = [] value = var.get().strip() if value and value.isdigit(): input_data['exclude_back'].append(int(value)) elif key.startswith('front_dan_'): if 'front_dan' not in input_data: input_data['front_dan'] = [] value = var.get().strip() if value and value.isdigit(): input_data['front_dan'].append(int(value)) elif key.startswith('back_dan_'): if 'back_dan' not in input_data: input_data['back_dan'] = [] value = var.get().strip() if value and value.isdigit(): input_data['back_dan'].append(int(value)) elif isinstance(var, StringVar): input_data[key] = var.get() elif isinstance(var, Text): input_data[key] = var.get(1.0, 'end').strip() # 创建模块运行事件 run_event = Event( event_id=int(time.time()), event_type=EventType.MODULE_RUN.value if hasattr(EventType.MODULE_RUN, 'value') else EventType.MODULE_RUN, source='main_ui', target=self.current_module, data={ 'token': self.token_manager.get_token(), 'input_data': input_data } ) # 发布事件 event_center.publish(run_event) self.status_var.set(f"正在运行 {AppConfig.MODULE_IDS[self.current_module]}...") except Exception as e: messagebox.showerror("错误", f"运行失败: {str(e)}") def _handle_module_result(self, event): """处理模块结果事件""" try: # 检查token是否有效 if event.data.get('token') != self.token_manager.get_token(): logger.warning( f"Token不匹配,忽略结果: {event.data.get('token')} != {self.token_manager.get_token()}") return module_id = event.data.get('module_id') result_data = event.data.get('data', {}) # 更新动态区显示 if module_id == 'input_analysis': if '推荐号码' in result_data: rec = result_data['推荐号码'] if '前区' in rec: self.var_refront.set(','.join(map(str, rec['前区']))) if '后区' in rec: self.var_reback.set(','.join(map(str, rec['后区']))) elif module_id == 'number_generation': if '生成号码' in result_data: gen_numbers = result_data['生成号码'] text_widget = self.dynamic_vars.get("generated_numbers") if text_widget: text_widget.delete(1.0, 'end') for i, num_str in enumerate(gen_numbers, 1): text_widget.insert('end', f"{i}. {num_str}\n") else: # 更新其他模块的显示 for key, value in result_data.items(): if key in self.dynamic_vars: if isinstance(value, list): self.dynamic_vars[key].set(','.join(map(str, value))) else: self.dynamic_vars[key].set(str(value)) self.status_var.set(f"{AppConfig.MODULE_IDS[module_id]} 完成") except Exception as e: logger.error(f"处理模块结果失败: {str(e)}") def _handle_module_error(self, event): """处理模块错误事件""" error_msg = event.data.get('error', '未知错误') self.status_var.set(f"错误: {error_msg}") messagebox.showerror("错误", error_msg) def _format_number(self, number): """格式化数字显示 - 静态方法""" if isinstance(number, list): return ','.join(map(str, number)) return str(number) def _get_exclude_numbers(self): """获取排除号码 - 静态方法""" exclude_front = [] exclude_back = [] for entry in self.exclude_front_entries: value = entry.get().strip() if value and value.isdigit(): exclude_front.append(int(value)) for entry in self.exclude_back_entries: value = entry.get().strip() if value and value.isdigit(): exclude_back.append(int(value)) return exclude_front, exclude_back # ====== 应用启动函数 ====== def main(): """主函数""" try: # 初始化号码池 number_pool = NumberPool() # 创建主窗口 root = Tk() app = MainWindow(root, number_pool) # 设置初始模块 app.select_module('input_analysis') # 启动主循环 root.mainloop() except Exception as e: logger.error(f"应用启动失败: {str(e)}") messagebox.showerror("启动错误", f"应用启动失败: {str(e)}") if __name__ == "__main__": main()
09-18
import binascii import serial import time import struct import queue import threading from datetime import datetime CanOBDItemList = [] CanPGNItemList = [[0,0,0,0]] filteredCanOBDItemList = [] Frame_start = b'\xFF' Frame_end = b'\x55' Frame_data_style_len = 6 Frame_Data_Len = 0 frame_buffer = bytearray() class CanInfShow_Item: def __int__(self,CanID,CanFramType,Len,CanDataInf): self.SystemCycle = datetime.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], self.CanID = CanID, self.CanFrame = CanFramType, self.CanDataLen = Len, self.CanData = CanDataInf class CanPGNShow_Item: def __int__(self, PGNID, CanID, CanData, Signal): self.PGNID = PGNID, self.CanID = CanID, self.CanData = CanData, self.Signal = Signal class SerialPort: def __init__(self, port, baudrate): # 初始化串口参数 self.port = port self.baudrate = baudrate self.ser = serial.Serial( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE ) # 等待串口连接稳定 self.last_data_time = time.time() # 新增:最后接收数据的时间戳 self.cycle_dict = {} # 存储{帧ID: [上次时间戳, 当前周期]} self.last_frame_time = {} # 存储每个ID的最后出现时间 self.data_updated_ids = set() # 存储数据变化的CAN ID self.new_added_ids = set() # 存储新增的CAN ID self.last_data_dict = {} # 存储每个CAN ID的上一次数据 self.changed_bytes_dict = {} # 存储每个CAN ID的变化字节索引 self.last_frame_num = {} self.last_pgn_data_dict = {} # 存储每个PGN ID的上一次数据 self.changed_pgn_bytes_dict = {} # 存储每个PGN ID的变化字节索引 self.state = 0 self.current_frame = bytearray() self.expected_length = 0 self.raw_data_queue = queue.Queue(maxsize=10000) self.data_lock = threading.Lock() self.worker_threads = [] self.allowed_pgn_ids = {0xFEF1, 0xF004, 0xFEC1, 0xFEE5, 0xFEEE, 0xFE56, 0xFEF2, 0xF005} self.filter_cycles = [] # 添加这一行用于存储过滤周期 #优化二维查找方式的映射字典 self.can_obd_index_map = {} self.filtered_index_map = {} self.can_pgn_index_map = {} time.sleep(0.2) if not self.ser.isOpen(): print("串口打开失败!") def close(self): # 关闭串口连接 if self.ser.isOpen(): self.ser.close() def send(self, data): # 发送数据 if self.ser.isOpen(): try: self.ser.write(data.encode('utf-8')) except serial.SerialException as e: print(f"发送数据失败: {e}") # def recv(self): # # 接收数据 # if self.ser.isOpen(): # data = self.ser.read(self.ser.inWaiting()) # if data: # 有数据时更新时间戳 # self.last_data_time = time.time() # return data def recv(self, chunk_size=1024): if self.ser.isOpen(): # 每次最多读取1024字节 data = self.ser.read(min(self.ser.inWaiting(), chunk_size)) if data: # self.last_data_time = time.time() return data return None def __del__(self): self.close() def SerialIsOpen(self): if self.ser.isOpen(): return 1 else: return 0 def start_reading(self): self.recv_thread = threading.Thread(target=self._recv_worker, daemon=True) self.parse_thread = threading.Thread(target=self._parse_worker, daemon=True) self.recv_thread.start() self.parse_thread.start() self.worker_threads = [self.recv_thread, self.parse_thread] def _recv_worker(self): while self.ser.isOpen(): data = self.recv(chunk_size=4096) # 每次最多读4KB if data: self.raw_data_queue.put(data) # else: # time.sleep(0.001) def _parse_worker(self): while True: try: data = self.raw_data_queue.get(timeout=0.1) for byte in data: self.process_byte(byte) except queue.Empty: continue def process_byte(self, byte): """ 使用状态机逐字节解析帧结构。 """ if self.state == 0: # 等待帧头 FF if byte == 0xFF: self.current_frame.append(byte) self.state = 1 else: # 如果不是帧头,忽略该字节 pass elif self.state == 1: # 等待帧头 55 if byte == 0x55: self.current_frame.append(byte) self.state = 2 else: # 如果第二字节不是55,重置 self.reset_state() elif self.state == 2: # 接收总长度低位 (第2字节) self.current_frame.append(byte) self.state = 3 elif self.state == 3: # 接收总长度高位 (第3字节) self.current_frame.append(byte) # 计算总长度(从第2字节开始) length_high = self.current_frame[2] length_low = byte self.expected_length = (length_high << 8) | length_low self.state = 4 elif self.state == 4: # 接收类型字段 (第4字节) self.current_frame.append(byte) self.state = 5 elif self.state == 5: # 接收保留字段 (第5字节) self.current_frame.append(byte) self.state = 6 elif self.state == 6: # 接收 CAN 通道类型 (第6字节) self.current_frame.append(byte) self.state = 7 elif self.state == 7: # 接收 CAN 报文个数 N (第7字节) self.current_frame.append(byte) self.num_messages = byte self.state = 8 self.messages_received = 0 elif self.state == 8: #接收can报文 self.current_frame.append(byte) self.messages_received += 1 if self.messages_received == self.num_messages * 12: self.state = 9 elif self.state == 9: # 接收校验位 self.current_frame.append(byte) self.state = 10 elif self.state == 10: # 完整帧已接收,验证校验和 if self.verify_checksum(): self.Frame_analoy_process(bytes(self.current_frame)) else: print("校验失败,丢弃当前帧") self.reset_state() def verify_checksum(self): """ 验证校验和:从第2字节到倒数第二个字节之和 & 0xFF """ data_to_check = self.current_frame[2:-1] # 从第2字节到最后一个校验位之前 checksum = sum(data_to_check) & 0xFF return checksum == self.current_frame[-1] def reset_state(self): """ 重置状态机 """ self.state = 0 self.current_frame = bytearray() self.expected_length = 0 self.messages_received = 0 def set_filter_cycles(self, cycles): self.filter_cycles = cycles.copy() if cycles else [] #报文解析 def Frame_analoy_process(self, Framedata): # 检查帧类型 (0x0C 0x98) if len(Framedata) < 8 or Framedata[4] != 0x0C or Framedata[5] != 0x98: return try: FrameNum = int(Framedata[7]) except IndexError: return # 检查是否有足够数据 if len(Framedata) < 12 * FrameNum + 8: return # current_time = time.time() # 获取当前精确时间戳 for index in range(0,FrameNum): # 时间戳 Cantime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] try: id_bytes = [ Framedata[12 * index + 11], # LSB Framedata[12 * index + 10], Framedata[12 * index + 9], Framedata[12 * index + 8] # MSB ] except IndexError: continue # 格式化为8位大写十六进制 CanID = ''.join(format(b, '02X') for b in id_bytes) # 提取ID字节 CanFramType = "Cycle" Len = 8 CanDataSpace = '' PGNdata = '' PGNID = int(Framedata[12 * index + 9] ) + int(Framedata[12 * index + 10]* 0x100) PGNSignl = self.Frame_analoy_PGN_Signal(PGNID,Framedata[12 * index + 12:]) # 提取数据部分 PGNdata = ''.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ).upper() try: CanDataSpace = ' '.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ) except IndexError: continue current_data = CanDataSpace.split() if CanID in self.last_data_dict: last_data = self.last_data_dict[CanID].split() changed_indices = [] for i in range(min(len(last_data), len(current_data))): if last_data[i] != current_data[i]: changed_indices.append(i) self.changed_bytes_dict[CanID] = changed_indices else: self.changed_bytes_dict[CanID] = [] # 新ID无变化 self.last_data_dict[CanID] = CanDataSpace CanItemData = [Cantime, CanID, CanFramType, Len, CanDataSpace] # print(CanDataSpace) # ✅ 只有在白名单内的PGNID才处理PGN信号 # 获取当前PGN数据 current_pgn_data = PGNdata # 使用上面生成的两位格式数据 # 检查数据变化 if PGNID in self.last_pgn_data_dict: last_data = self.last_pgn_data_dict[PGNID] changed_indices = [] for i in range(min(len(last_data), len(current_pgn_data) // 2)): start_idx = i * 2 end_idx = start_idx + 2 if last_data[start_idx:end_idx] != current_pgn_data[start_idx:end_idx]: changed_indices.append(i) self.changed_pgn_bytes_dict[PGNID] = changed_indices else: self.changed_pgn_bytes_dict[PGNID] = [] # 新PGN ID无变化 self.last_pgn_data_dict[PGNID] = current_pgn_data if PGNID in self.allowed_pgn_ids: PGNSignl = self.Frame_analoy_PGN_Signal(PGNID, Framedata[12 * index + 12:]) SignalItemData = [hex(PGNID)[2:].upper(), CanID, PGNdata, PGNSignl] if all(not sublist for sublist in CanPGNItemList) or CanPGNItemList[0][0] == 0: if CanPGNItemList: removed_id = CanPGNItemList[0][1] self.remove_from_index_maps("CanPGNItemList", removed_id) CanPGNItemList.pop(0) if PGNID in self.allowed_pgn_ids: PGNSignl = self.Frame_analoy_PGN_Signal(PGNID, Framedata[12 * index + 12:]) SignalItemData = [hex(PGNID)[2:].upper(), CanID, PGNdata, PGNSignl] # 使用快速查找 index_in_list = self.find_index("CanPGNItemList", CanID) if index_in_list is not None: CanPGNItemList[index_in_list] = SignalItemData else: CanPGNItemList.append(SignalItemData) new_index = len(CanPGNItemList) - 1 self.update_index_maps("CanPGNItemList", new_index, CanID) if index == 0: current_time = time.time() if CanID in self.last_frame_time: last_time = self.last_frame_time[CanID] cycle = (current_time - last_time) * 1000/self.last_frame_num[CanID] # 转换为毫秒 self.last_frame_time[CanID]=current_time self.cycle_dict[CanID] = cycle if CanID == "18F00F52" : print(cycle) # if cycle > 10: # if cycle<self.cycle_dict[CanID] : # self.cycle_dict[CanID] = cycle # elif self.cycle_dict[CanID] == 0 : # self.cycle_dict[CanID] = cycle else: self.cycle_dict[CanID] = 0 # 首次出现,周期设为0 self.last_frame_time[CanID] = current_time # if "18F00F52" in self.cycle_dict: # print(self.cycle_dict["18F00F52"]) # 更新列表 filtered_cycles = getattr(self, 'filter_cycles', []) is_filtered = False if filtered_cycles: for filter_cycle in filtered_cycles: tolerance = filter_cycle * 0.15 # 允许±15%的容差 if abs(self.cycle_dict[CanID] - filter_cycle) <= tolerance: is_filtered = True break index_in_obd = self.find_index("CanOBDItemList", CanID) index_in_filtered = self.find_index("filteredCanOBDItemList", CanID) # 根据过滤状态更新相应的列表 if is_filtered: if index_in_filtered is not None: filteredCanOBDItemList[index_in_filtered] = CanItemData self.data_updated_ids.add(CanID) else: # 如果存在于主列表,先移除 if index_in_obd is not None: self.remove_from_index_maps("CanOBDItemList", CanID) CanOBDItemList.pop(index_in_obd) self.data_updated_ids.add(CanID) filteredCanOBDItemList.append(CanItemData) new_index = len(filteredCanOBDItemList) - 1 self.update_index_maps("filteredCanOBDItemList", new_index, CanID) self.new_added_ids.add(CanID) else: index_in_list = self.find_index("CanOBDItemList", CanID) if index_in_obd is not None: CanOBDItemList[index_in_obd] = CanItemData self.data_updated_ids.add(CanID) else: # 如果存在于过滤列表,先移除 if index_in_filtered is not None: self.remove_from_index_maps("filteredCanOBDItemList", CanID) filteredCanOBDItemList.pop(index_in_filtered) CanOBDItemList.append(CanItemData) new_index = len(CanOBDItemList) - 1 self.update_index_maps("CanOBDItemList", new_index, CanID) self.new_added_ids.add(CanID) self.last_frame_num[CanID] = FrameNum # if CanID == "18F00F52": # print(FrameNum) def find_index(self, list_name, can_id): """快速查找索引的O(1)方法""" if list_name == "CanOBDItemList": return self.can_obd_index_map.get(can_id) elif list_name == "filteredCanOBDItemList": return self.filtered_index_map.get(can_id) elif list_name == "CanPGNItemList": return self.can_pgn_index_map.get(can_id) return None def update_index_maps(self, list_name, index, can_id): """更新索引映射""" if list_name == "CanOBDItemList": self.can_obd_index_map[can_id] = index elif list_name == "filteredCanOBDItemList": self.filtered_index_map[can_id] = index elif list_name == "CanPGNItemList": self.can_pgn_index_map[can_id] = index def remove_from_index_maps(self, list_name, can_id): """从索引映射中移除条目""" if list_name == "CanOBDItemList": self.can_obd_index_map.pop(can_id, None) elif list_name == "filteredCanOBDItemList": self.filtered_index_map.pop(can_id, None) elif list_name == "CanPGNItemList": self.can_pgn_index_map.pop(can_id, None) def Frame_analoy_PGN_Signal(self, PGNID, Framedata): # 确保数据是整数列表(0-255) if not all(isinstance(x, int) for x in Framedata): Framedata = [int(x) for x in Framedata] # 根据J1939规范解析 if PGNID == 0xFEF1: # 车速 (CCVS1) # 位置2-3 (索引1-2),大端序,单位1/256 km/h raw_val = (Framedata[1] << 8) | Framedata[2] return raw_val / 256.0 elif PGNID == 0xFE6C: # 车速 (TCO1) - 新增 # 位置7-8 (索引6-7),大端序,单位1/256 km/h raw_val = (Framedata[6] << 8) | Framedata[7] return raw_val / 256.0 elif PGNID == 0xF004: # 发动机转速+负载 # 负载:位置3 (索引2),单位1% engine_load = Framedata[2] & 0x7F # 取7位 # 转速:位置4-5 (索引3-4),大端序,单位0.125 RPM raw_rpm = (Framedata[3] << 8) | Framedata[4] rpm = raw_rpm * 0.125 return f'{engine_load}|{rpm}' elif PGNID == 0xFEC1: # 里程表 (VDHR) # 位置1-4 (索引0-3),大端序,单位0.125米 raw_val = int(Framedata[3] * 0x1000000) + int(Framedata[2] * 0x10000) + int(Framedata[1] * 0x100) + int(Framedata[0]) return raw_val * 0.125 # 转换为米 elif PGNID == 0xFEE5: # 发动机小时数 # 位置1-4 (索引0-3),大端序,单位0.05小时 raw_val = (Framedata[0] << 24) | (Framedata[1] << 16) | (Framedata[2] << 8) | Framedata[3] return raw_val * 0.05 elif PGNID == 0xFEF2: # 平均油耗 # 位置1-2 (索引0-1),大端序,单位0.05 L/h raw_val = (Framedata[0] << 8) | Framedata[1] return raw_val * 0.05 elif PGNID == 0xFEEE: # 冷却液温度 # 位置1 (索引0),单位1°C,偏移-40 return Framedata[0] - 40 elif PGNID == 0xFE56: # 燃油液位 # 位置1 (索引0),单位0.4% return Framedata[0] * 0.4 elif PGNID == 0xF005: # 档位 # 位置4 (索引3),直接返回值 return Framedata[3] return None 程序报错了 File "D:\PycharmProjects\PythonCode\UI\SerialPro.py", line 346, in Frame_analoy_process cycle = (current_time - last_time) * 1000/self.last_frame_num[CanID] # 转换为毫秒 KeyError: '0CF00203'
07-23
import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext import pandas as pd import os import threading import tempfile import zipfile import shutil import configparser from xml.etree import ElementTree as ET from openpyxl import load_workbook import re import datetime # 辅助函数:将Excel列字母转换为索引(A=0, B=1, ..., AA=26等) def excel_column_letter_to_index(letter): """Convert Excel column letter to zero-based index""" index = 0 for char in letter: index = index * 26 + (ord(char.upper()) - ord('A') + 1) return index - 1 class ExcelComparatorApp: def __init__(self, root): self.root = root self.root.title("Excel 文件比较工具") self.root.geometry("1000x950") self.root.minsize(900, 600) # 加载配置 self.config = configparser.ConfigParser() self.config_path = os.path.join(os.path.expanduser("~"), "excel_comparator_config.ini") self.load_config() # 初始化配置相关的属性 self.initialize_config_based_attributes() # 配置样式 self.style = ttk.Style() self.style.configure("TFrame", padding=10) self.style.configure("TButton", padding=6) self.style.configure("TLabel", padding=5) self.style.configure("TCheckbutton", padding=5) self.style.configure("Accent.TButton", background="#4CAF50", foreground="white", font=("Arial", 10, "bold")) # 主容器使用网格布局管理器 self.main_frame = ttk.Frame(root) self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建界面 self.create_interface() # 初始化变量 self.old_file = "" self.new_file = "" # 确保界面元素可见 self.root.update_idletasks() # 初始化后尝试加载列名 self.root.after(100, self.try_load_columns) def initialize_config_based_attributes(self): """初始化所有配置相关的属性""" # 文件路径 self.old_file = "" self.new_file = "" # 表格设置 self.skiprows = self.config.getint('SETTINGS', 'skiprows', fallback=9) # 列范围(使用Excel字母格式) self.signal_start_col = 0 self.signal_end_col = 12 self.data_start_col = 53 self.data_end_col = 55 # 报告类型变量 self.generate_text_report = tk.BooleanVar( value=self.config.getboolean('SETTINGS', 'generate_text_report', fallback=True)) self.generate_excel_report = tk.BooleanVar( value=self.config.getboolean('SETTINGS', 'generate_excel_report', fallback=True)) # 列加载基准文件选项 self.col_load_based_on = tk.StringVar( value=self.config.get('SETTINGS', 'col_load_based_on', fallback='old')) # 列选择变量 self.signal_col_vars = {} self.data_col_vars = {} # 初始化其他变量 self.old_file_entry = None self.new_file_entry = None self.old_sheet_var = None self.new_sheet_var = None self.output_path_entry = None self.log_text = None self.status_var = None self.compare_btn = None # 进度条相关 self.progress = None self.status_label = None # 初始化 skiprows_var self.skiprows_var = tk.StringVar(value=str(self.skiprows)) # 列范围变量 self.signal_start_var = tk.StringVar(value=self.config.get('SETTINGS', 'signal_start', fallback='A')) self.signal_end_var = tk.StringVar(value=self.config.get('SETTINGS', 'signal_end', fallback='M')) self.data_start_var = tk.StringVar(value=self.config.get('SETTINGS', 'data_start', fallback='BT')) self.data_end_var = tk.StringVar(value=self.config.get('SETTINGS', 'data_end', fallback='CD')) # 报告类型变量 self.text_report_var = tk.BooleanVar(value=self.generate_text_report.get()) self.excel_report_var = tk.BooleanVar(value=self.generate_excel_report.get()) def try_load_columns(self): """尝试在界面初始化后加载列名(如果文件已存在)""" if self.old_file_entry.get() or self.new_file_entry.get(): self.load_columns_from_file() def load_config(self): """加载配置文件""" if os.path.exists(self.config_path): try: self.config.read(self.config_path) except: self.create_default_config() else: self.create_default_config() def create_default_config(self): """创建默认配置""" self.config['SETTINGS'] = { 'skiprows': '9', 'signal_start': 'A', 'signal_end': 'M', 'data_start': 'BT', 'data_end': 'CD', 'generate_text_report': 'True', 'generate_excel_report': 'True', 'col_load_based_on': 'old', 'output_path': '' } self.save_config() def save_config(self): """保存配置到文件""" with open(self.config_path, 'w') as configfile: self.config.write(configfile) def create_interface(self): """创建界面元素 - 使用网格布局""" # 创建行计数器 row = 0 # 文件选择部分 file_frame = ttk.LabelFrame(self.main_frame, text="文件选择") file_frame.grid(row=row, column=0, sticky="ew", padx=5, pady=5) row += 1 # 旧文件选择 ttk.Label(file_frame, text="旧Excel文件:").grid(row=0, column=0, sticky="w", padx=5, pady=5) self.old_file_entry = ttk.Entry(file_frame, width=60) self.old_file_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew") self.old_file_entry.insert(0, self.config.get('SETTINGS', 'old_file', fallback='')) ttk.Button( file_frame, text="浏览...", command=lambda: self.browse_file(self.old_file_entry), width=10 ).grid(row=0, column=2, padx=5, pady=5) # 旧文件Sheet配置 ttk.Label(file_frame, text="Sheet名称或索引:").grid(row=0, column=3, sticky="w", padx=(15, 5), pady=5) self.old_sheet_var = tk.StringVar(value=self.config.get('SETTINGS', 'old_sheet', fallback='')) self.old_sheet_entry = ttk.Entry(file_frame, textvariable=self.old_sheet_var, width=20) self.old_sheet_entry.grid(row=0, column=4, padx=5, pady=5, sticky="w") # 新文件选择 ttk.Label(file_frame, text="新Excel文件:").grid(row=1, column=0, sticky="w", padx=5, pady=5) self.new_file_entry = ttk.Entry(file_frame, width=60) self.new_file_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew") self.new_file_entry.insert(0, self.config.get('SETTINGS', 'new_file', fallback='')) ttk.Button( file_frame, text="浏览...", command=lambda: self.browse_file(self.new_file_entry), width=10 ).grid(row=1, column=2, padx=5, pady=5) # 新文件Sheet配置 ttk.Label(file_frame, text="Sheet名称或索引:").grid(row=1, column=3, sticky="w", padx=(15, 5), pady=5) self.new_sheet_var = tk.StringVar(value=self.config.get('SETTINGS', 'new_sheet', fallback='')) self.new_sheet_entry = ttk.Entry(file_frame, textvariable=self.new_sheet_var, width=20) self.new_sheet_entry.grid(row=1, column=4, padx=5, pady=5, sticky="w") file_frame.columnconfigure(1, weight=1) # 设置按钮 settings_btn = ttk.Button( self.main_frame, text="高级设置", command=self.open_settings_dialog, width=15 ) settings_btn.grid(row=row, column=0, sticky="e", padx=5, pady=5) row += 1 # 列选择部分 cols_frame = ttk.LabelFrame(self.main_frame, text="选择要比较的列 (文件选择后自动加载)") cols_frame.grid(row=row, column=0, sticky="nsew", padx=5, pady=5) # 配置网格权重 self.main_frame.rowconfigure(row, weight=1) self.main_frame.columnconfigure(0, weight=1) cols_frame.columnconfigure(0, weight=1) cols_frame.rowconfigure(0, weight=1) # 创建两个面板容器 paned_window = ttk.PanedWindow(cols_frame, orient=tk.HORIZONTAL) paned_window.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) cols_frame.columnconfigure(0, weight=1) cols_frame.rowconfigure(0, weight=1) # 信号级别列 signal_frame = ttk.LabelFrame(paned_window, text="信号级别列") signal_frame.grid(row=0, column=0, sticky="nsew") # 信号级别列的滚动区域 signal_scroll = ttk.Scrollbar(signal_frame) signal_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.signal_canvas = tk.Canvas(signal_frame, yscrollcommand=signal_scroll.set) self.signal_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) signal_scroll.config(command=self.signal_canvas.yview) self.signal_inner_frame = ttk.Frame(self.signal_canvas) self.signal_canvas.create_window((0, 0), window=self.signal_inner_frame, anchor="nw") self.signal_inner_frame.bind( "<Configure>", lambda e: self.signal_canvas.configure(scrollregion=self.signal_canvas.bbox("all")) ) paned_window.add(signal_frame, weight=1) # 数据级别列 data_frame = ttk.LabelFrame(paned_window, text="数据级别列") data_frame.grid(row=0, column=1, sticky="nsew") # 数据级别列的滚动区域 data_scroll = ttk.Scrollbar(data_frame) data_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.data_canvas = tk.Canvas(data_frame, yscrollcommand=data_scroll.set) self.data_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) data_scroll.config(command=self.data_canvas.yview) self.data_inner_frame = ttk.Frame(self.data_canvas) self.data_canvas.create_window((0, 0), window=self.data_inner_frame, anchor="nw") self.data_inner_frame.bind( "<Configure>", lambda e: self.data_canvas.configure(scrollregion=self.data_canvas.bbox("all")) ) paned_window.add(data_frame, weight=1) # 绑定鼠标滚轮事件 self.signal_canvas.bind("<MouseWheel>", lambda e: self.signal_canvas.yview_scroll(int(-1*(e.delta/120)), "units")) self.data_canvas.bind("<MouseWheel>", lambda e: self.data_canvas.yview_scroll(int(-1*(e.delta/120)), "units")) row += 1 # 输出路径 output_frame = ttk.LabelFrame(self.main_frame, text="输出设置") output_frame.grid(row=row, column=0, sticky="ew", padx=5, pady=5) row += 1 # 输出路径 ttk.Label(output_frame, text="输出路径:").grid(row=0, column=0, sticky="w", padx=5, pady=5) self.output_path_entry = ttk.Entry(output_frame) self.output_path_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew") self.output_path_entry.insert(0, self.config.get('SETTINGS', 'output_path', fallback='')) ttk.Button( output_frame, text="浏览...", command=self.browse_output_path, width=10 ).grid(row=0, column=2, padx=5, pady=5) output_frame.columnconfigure(1, weight=1) # 按钮区域 button_frame = ttk.Frame(self.main_frame) button_frame.grid(row=row, column=0, sticky="ew", padx=5, pady=10) row += 1 # 比较按钮 self.compare_btn = ttk.Button( button_frame, text="开始比较", command=self.start_comparison_thread, width=30, style="Accent.TButton" ) self.compare_btn.pack(pady=10) # 进度条 self.progress = ttk.Progressbar( button_frame, orient=tk.HORIZONTAL, length=400, mode='indeterminate' ) self.progress.pack(pady=5, fill=tk.X, expand=True) self.progress.pack_forget() # 初始隐藏 # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.grid(row=row, column=0, sticky="ew", padx=5, pady=5) row += 1 # 日志区域 log_frame = ttk.LabelFrame(self.main_frame, text="日志输出") log_frame.grid(row=row, column=0, sticky="nsew", padx=5, pady=5) self.main_frame.rowconfigure(row, weight=1) # 日志区域获得额外空间 # 日志文本区域 self.log_text = scrolledtext.ScrolledText( log_frame, wrap=tk.WORD, height=10 ) self.log_text.pack(fill="both", expand=True, padx=5, pady=5) self.log_text.config(state=tk.DISABLED) # 添加标签样式 self.log_text.tag_config("error", foreground="red") self.log_text.tag_config("success", foreground="green") self.log_text.tag_config("warning", foreground="orange") # 添加清除日志按钮 ▼▼▼ 新增代码 ▼▼▼ clear_log_frame = ttk.Frame(log_frame) clear_log_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Button( clear_log_frame, text="清除日志", command=self.clear_log, width=10 ).pack(side=tk.RIGHT, padx=5, pady=2) # ▼▼▼ 新增方法:清除日志 ▼▼▼ def clear_log(self): """清除日志内容""" self.log_text.config(state=tk.NORMAL) self.log_text.delete(1.0, tk.END) self.log_text.config(state=tk.DISABLED) self.log("日志已清除") def open_settings_dialog(self): """打开设置对话框""" dialog = tk.Toplevel(self.root) dialog.title("高级设置") dialog.geometry("550x500") dialog.transient(self.root) dialog.grab_set() # 对话框框架 main_frame = ttk.Frame(dialog, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 列名行配置 row_frame = ttk.LabelFrame(main_frame, text="列名行配置") row_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(row_frame, text="列名所在行索引 (0表示第一行):").grid(row=0, column=0, sticky="w", padx=5, pady=5) skiprows_entry = ttk.Entry(row_frame, textvariable=self.skiprows_var, width=10) skiprows_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w") # 列范围配置 range_frame = ttk.LabelFrame(main_frame, text="列范围配置 (Excel列字母)") range_frame.pack(fill=tk.X, padx=5, pady=5) # 信号级别列范围 ttk.Label(range_frame, text="信号级别列范围:").grid(row=0, column=0, sticky="w", padx=5, pady=5) signal_start_entry = ttk.Entry(range_frame, textvariable=self.signal_start_var, width=5) signal_start_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w") ttk.Label(range_frame, text="至").grid(row=0, column=2, padx=5, pady=5) signal_end_entry = ttk.Entry(range_frame, textvariable=self.signal_end_var, width=5) signal_end_entry.grid(row=0, column=3, padx=5, pady=5, sticky="w") # 数据级别列范围 ttk.Label(range_frame, text="数据级别列范围:").grid(row=1, column=0, sticky="w", padx=5, pady=5) data_start_entry = ttk.Entry(range_frame, textvariable=self.data_start_var, width=5) data_start_entry.grid(row=1, column=1, padx=5, pady=5, sticky="w") ttk.Label(range_frame, text="至").grid(row=1, column=2, padx=5, pady=5) data_end_entry = ttk.Entry(range_frame, textvariable=self.data_end_var, width=5) data_end_entry.grid(row=1, column=3, padx=5, pady=5, sticky="w") # 列加载基准文件设置 col_based_frame = ttk.LabelFrame(main_frame, text="列加载基准文件") col_based_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Radiobutton( col_based_frame, text="基于旧文件加载列", variable=self.col_load_based_on, value="old" ).pack(anchor=tk.W, padx=10, pady=3) ttk.Radiobutton( col_based_frame, text="基于新文件加载列", variable=self.col_load_based_on, value="new" ).pack(anchor=tk.W, padx=10, pady=3) # 输出设置 output_frame = ttk.LabelFrame(main_frame, text="输出设置") output_frame.pack(fill=tk.X, padx=5, pady=5) # 报告输出 ttk.Checkbutton( output_frame, text="生成文本报告", variable=self.text_report_var ).pack(anchor=tk.W, padx=10, pady=5) ttk.Checkbutton( output_frame, text="生成Excel报告", variable=self.excel_report_var ).pack(anchor=tk.W, padx=10, pady=5) # 应用按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button( btn_frame, text="应用设置", command=lambda: self.apply_settings(dialog), width=15 ).pack(side=tk.RIGHT, padx=5) ttk.Button( btn_frame, text="保存设置", command=self.save_settings, width=15 ).pack(side=tk.RIGHT, padx=5) ttk.Button( btn_frame, text="重置为默认", command=self.reset_settings, width=15 ).pack(side=tk.LEFT, padx=5) def reset_settings(self): """重置设置为默认值""" self.skiprows_var.set('9') self.signal_start_var.set('A') self.signal_end_var.set('M') self.data_start_var.set('BT') self.data_end_var.set('CD') self.text_report_var.set(True) self.excel_report_var.set(True) self.col_load_based_on.set('old') self.log("设置已重置为默认值") def save_settings(self): """保存当前设置""" try: # 更新配置对象 self.config['SETTINGS'] = { 'skiprows': self.skiprows_var.get(), 'signal_start': self.signal_start_var.get(), 'signal_end': self.signal_end_var.get(), 'data_start': self.data_start_var.get(), 'data_end': self.data_end_var.get(), 'generate_text_report': str(self.text_report_var.get()), 'generate_excel_report': str(self.excel_report_var.get()), 'col_load_based_on': self.col_load_based_on.get(), 'old_file': self.old_file_entry.get(), 'new_file': self.new_file_entry.get(), 'old_sheet': self.old_sheet_var.get(), 'new_sheet': self.new_sheet_var.get(), 'output_path': self.output_path_entry.get() } # 保存到文件 self.save_config() self.log("设置已成功保存") except Exception as e: self.log(f"保存设置失败: {str(e)}", error=True) def apply_settings(self, dialog=None): """应用设置并关闭对话框""" try: # 应用列范围设置 self.apply_column_range() # 保存设置 self.save_settings() # 重新加载列 self.load_columns_from_file() # 关闭对话框 if dialog: dialog.destroy() except Exception as e: self.log(f"设置应用错误: {str(e)}", error=True) def browse_file(self, entry_widget): """浏览文件并设置到输入框""" file_path = filedialog.askopenfilename( filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: entry_widget.delete(0, tk.END) entry_widget.insert(0, file_path) # 文件选择后自动加载列 self.load_columns_from_file() def browse_output_path(self): """浏览输出路径""" dir_path = filedialog.askdirectory() if dir_path: self.output_path_entry.delete(0, tk.END) self.output_path_entry.insert(0, dir_path) def apply_column_range(self): """应用列范围设置(使用Excel字母格式)""" try: # 将字母转换为数字索引 self.signal_start_col = excel_column_letter_to_index(self.signal_start_var.get()) self.signal_end_col = excel_column_letter_to_index(self.signal_end_var.get()) self.data_start_col = excel_column_letter_to_index(self.data_start_var.get()) self.data_end_col = excel_column_letter_to_index(self.data_end_var.get()) self.log(f"列范围设置已应用: 信号列[{self.signal_start_var.get()} ({self.signal_start_col}) - {self.signal_end_var.get()} ({self.signal_end_col})], 数据列[{self.data_start_var.get()} ({self.data_start_col}) - {self.data_end_var.get()} ({self.data_end_col})]") except ValueError: self.log("错误: 请输入有效的列范围索引", error=True) def load_columns_from_file(self): """从文件中加载列名(显示索引位置)并根据范围过滤""" try: # 确定基于哪个文件加载列 if self.col_load_based_on.get() == 'old': file_path = self.old_file_entry.get() sheet_name = self.old_sheet_var.get().strip() else: file_path = self.new_file_entry.get() sheet_name = self.new_sheet_var.get().strip() if not file_path: return skiprows = int(self.skiprows_var.get()) if self.skiprows_var.get().isdigit() else 9 # 如果sheet_name为空,则使用0(第一个sheet) if not sheet_name and sheet_name != "0": sheet_name = 0 else: try: sheet_name = int(sheet_name) except ValueError: pass # 保持为字符串 df = self.safe_read_excel(file_path, skiprows, sheet_name) if df is None: self.log(f"无法加载文件: {file_path}", error=True) return # 清空现有列选择 for widget in self.signal_inner_frame.winfo_children(): widget.destroy() for widget in self.data_inner_frame.winfo_children(): widget.destroy() # 创建列选择复选框 self.signal_col_vars = {} self.data_col_vars = {} # 获取配置的列范围 signal_start = self.signal_start_col signal_end = self.signal_end_col data_start = self.data_start_col data_end = self.data_end_col # 信号级别列 for i, col in enumerate(df.columns): if signal_start <= i <= signal_end: var = tk.BooleanVar(value=True) cb = ttk.Checkbutton( self.signal_inner_frame, text=f"{i}: {col}", variable=var ) cb.pack(anchor=tk.W, padx=5, pady=2) self.signal_col_vars[col] = var # 数据级别列 for i, col in enumerate(df.columns): if data_start <= i <= data_end: var = tk.BooleanVar(value=True) cb = ttk.Checkbutton( self.data_inner_frame, text=f"{i}: {col}", variable=var ) cb.pack(anchor=tk.W, padx=5, pady=2) self.data_col_vars[col] = var self.log(f"列名已从文件加载 ({self.col_load_based_on.get()}文件)") except Exception as e: self.log(f"加载列名失败: {str(e)}", error=True) def start_comparison_thread(self): """启动比较线程""" # 禁用按钮避免重复点击 self.compare_btn.config(state=tk.DISABLED) self.status_var.set("正在比较...") # 显示进度条 self.progress.pack(pady=5, fill=tk.X, expand=True) self.progress.start(10) # 获取输入参数 self.old_file = self.old_file_entry.get() self.new_file = self.new_file_entry.get() self.skiprows = int(self.skiprows_var.get()) if self.skiprows_var.get().isdigit() else 9 # 验证文件是否存在 if not os.path.exists(self.old_file) or not os.path.exists(self.new_file): self.log("错误: 文件不存在", error=True) self.comparison_finished() return # 获取选择的列 self.signal_cols = [col for col, var in self.signal_col_vars.items() if var.get()] self.data_cols = [col for col, var in self.data_col_vars.items() if var.get()] if not self.signal_cols or not self.data_cols: self.log("错误: 请选择要比较的列", error=True) self.comparison_finished() return # 获取新旧文件的Sheet名称 old_sheet = self.old_sheet_var.get().strip() new_sheet = self.new_sheet_var.get().strip() # 如果为空,则使用第一个Sheet if not old_sheet and old_sheet != "0": old_sheet = 0 else: try: old_sheet = int(old_sheet) except: pass # 保持为字符串 if not new_sheet and new_sheet != "0": new_sheet = 0 else: try: new_sheet = int(new_sheet) except: pass # 启动后台线程 threading.Thread(target=self.compare_excel_wrapper, args=(old_sheet, new_sheet), daemon=True).start() def compare_excel_wrapper(self, old_sheet, new_sheet): """比较Excel文件的包装函数""" try: # 执行比较 comparison_result = self.compare_excel(self.old_file, self.new_file, self.skiprows, old_sheet, new_sheet) if comparison_result is not None and not comparison_result.empty: # 保存结果 output_dir = self.output_path_entry.get() or os.getcwd() if not os.path.exists(output_dir): try: os.makedirs(output_dir) self.log(f"创建输出目录: {output_dir}") except PermissionError: # 如果没有权限,使用临时目录 temp_dir = tempfile.mkdtemp() self.log(f"无法创建输出目录,使用临时目录: {temp_dir}", warning=True) output_dir = temp_dir # 生成Excel报告 (改进的保存机制) if self.excel_report_var.get(): # 确保文件名唯一 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") excel_filename = f"comparison_result_{timestamp}.xlsx" excel_path = os.path.join(output_dir, excel_filename) try: # 尝试保存 comparison_result.to_excel(excel_path, index=False) self.log(f"Excel报告保存到: {excel_path}", success=True) # 尝试打开文件以确保没有锁定 with open(excel_path, 'rb') as test_file: test_file.read(1) except (PermissionError, OSError) as e: # 如果保存失败,尝试使用临时目录 temp_dir = tempfile.mkdtemp() temp_excel_path = os.path.join(temp_dir, excel_filename) comparison_result.to_excel(temp_excel_path, index=False) self.log(f"原始保存失败,文件已保存到临时目录: {temp_excel_path}", warning=True) self.log(f"错误详情: {str(e)}", warning=True) # 生成文本报告 (改进的保存机制) if self.text_report_var.get(): # 确保文件名唯一 text_filename = f"comparison_report_{timestamp}.txt" report_path = os.path.join(output_dir, text_filename) try: report = self.generate_report(comparison_result) with open(report_path, 'w', encoding='utf-8') as f: f.write(report) self.log(f"文本报告保存到: {report_path}", success=True) except (PermissionError, OSError) as e: # 如果保存失败,尝试使用临时目录 temp_dir = tempfile.mkdtemp() temp_report_path = os.path.join(temp_dir, text_filename) with open(temp_report_path, 'w', encoding='utf-8') as f: f.write(report) self.log(f"原始保存失败,文件已保存到临时目录: {temp_report_path}", warning=True) self.log(f"错误详情: {str(e)}", warning=True) self.log(f"比较完成! 发现 {len(comparison_result)} 处变更", success=True) else: self.log("比较完成! 未发现变更", success=True) except Exception as e: self.log(f"比较过程中发生错误: {str(e)}", error=True) import traceback self.log(traceback.format_exc(), error=True) finally: self.comparison_finished() def comparison_finished(self): """比较完成后的清理工作""" self.progress.stop() self.progress.pack_forget() # 隐藏进度条 self.compare_btn.config(state=tk.NORMAL) self.status_var.set("完成") def log(self, message, error=False, success=False): """记录日志消息""" self.log_text.config(state=tk.NORMAL) if error: self.log_text.insert(tk.END, "[错误] ") self.log_text.tag_add("error", "end-1c linestart", "end-1c lineend") elif success: self.log_text.insert(tk.END, "[成功] ") self.log_text.tag_add("success", "end-1c linestart", "end-1c lineend") else: self.log_text.insert(tk.END, "[信息] ") self.log_text.insert(tk.END, message + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) # ======================== 以下是核心比较功能 ======================== def safe_read_excel(self, file_path, skiprows, sheet_name=None): """安全的Excel读取函数,包含自动修复功能和Sheet选择""" if sheet_name is None: sheet_name = 0 # 默认为第一个Sheet # 最后尝试:使用二进制模式读取 self.log("二进制读取") try: with open(file_path, 'rb') as f: # 尝试不同的引擎 for engine in ['openpyxl', 'xlrd', 'odf']: try: df = pd.read_excel( f, skiprows=skiprows, dtype=str, engine=engine, sheet_name=sheet_name ).fillna('') self.log(f"成功使用引擎: {engine}", success=True) return df except: continue except Exception as e: self.log(f"二进制读取失败: {str(e)}", error=True) # 终极手段:手动提取数据 self.log("所有方法失败,尝试手动提取数据") try: wb = load_workbook(file_path, read_only=True, data_only=True, keep_vba=False) # 获取指定Sheet if isinstance(sheet_name, int): if sheet_name < len(wb.sheetnames): ws = wb.worksheets[sheet_name] else: ws = wb.active else: if sheet_name in wb.sheetnames: ws = wb[sheet_name] else: ws = wb.active data = [] for i, row in enumerate(ws.iter_rows(values_only=True)): if i < skiprows: continue data.append(row) headers = data[0] if data else [] df = pd.DataFrame(data[1:], columns=headers) return df.fillna('').astype(str) except Exception as e: raise ValueError(f"无法读取文件 {file_path}: {str(e)}") def find_met_columns(self, df, is_old_file=True): """ 查找MET列 - 旧文件:查找包含"MET"的列(V列和W列) - 新文件:查找包含"MET"的列(AE列) """ met_cols = [] # 尝试通过列名匹配 for col in df.columns: if "MET" in col.upper(): met_cols.append(col) # 如果未找到,尝试按位置查找 if not met_cols: self.log(f"警告: 通过列名未找到MET列,尝试按位置查找") if is_old_file: # 旧文件:V列(22)和W列(23),索引21和22 if len(df.columns) > 22: met_cols = [df.columns[21], df.columns[22]] self.log(f"使用位置索引的MET列: {met_cols}") elif len(df.columns) > 21: met_cols = [df.columns[21]] else: # 新文件:AE列(31),索引30 if len(df.columns) > 30: met_cols = [df.columns[30]] self.log(f"使用位置索引的MET列: {met_cols}") if not met_cols: self.log(f"严重警告: 未找到任何MET列,创建虚拟列以避免错误") # 创建虚拟列 df['MET_DUMMY'] = '' met_cols = ['MET_DUMMY'] return met_cols def check_met_status(self, row, met_cols): """ 检查行的MET状态 如果任一MET列包含'T'或'R',则返回True """ for col in met_cols: if col in row: met_value = str(row[col]).strip().upper() if met_value in ['T', 'R']: return True return False def build_signal_hierarchy(self, df, signal_col, data_col): """构建信号-数据的层级结构""" signals = {} current_signal = None for idx, row in df.iterrows(): # 检查是否是信号行(信号名列非空) row_data = row.to_dict() # 转换为字典以便处理 if pd.notna(row_data.get(signal_col)) and str(row_data.get(signal_col)).strip(): # 新信号开始 current_signal = str(row_data.get(signal_col)).strip() signals[current_signal] = { 'signal_row': row_data, 'data_items': [] } elif current_signal and pd.notna(row_data.get(data_col)) and str(row_data.get(data_col)).strip(): # 数据项属于当前信号 signals[current_signal]['data_items'].append(row_data) return signals def normalize_col_name(self, col): """标准化列名:移除非单词字符,空格,并转为小写""" return re.sub(r'[^\w]', '', str(col).replace(' ', '').lower()) def compare_excel(self, old_file, new_file, skiprows, old_sheet, new_sheet): # 安全读取文件 self.log(f"正在读取旧文件: {old_file} (Sheet: {old_sheet})") df_old = self.safe_read_excel(old_file, skiprows, old_sheet) self.log(f"正在读取新文件: {new_file} (Sheet: {new_sheet})") df_new = self.safe_read_excel(new_file, skiprows, new_sheet) # === 列名标准化函数 === def normalize_col_name(col): return re.sub(r'[^\w]', '', str(col).replace(' ', '').lower()) # 标准化列名 df_old.columns = [normalize_col_name(col) for col in df_old.columns] df_new.columns = [normalize_col_name(col) for col in df_new.columns] # === 关键列检测 === # 信号列 SIGNAL_COLS = ['フレーム名', 'フレーム名', 'framename', 'signalname'] signal_col = next((col for col in SIGNAL_COLS if col in df_old.columns), df_old.columns[0]) # 数据列 DATA_COLS = ['データ名', 'データ名', 'dataname', 'itemname'] data_col = next((col for col in DATA_COLS if col in df_old.columns), df_old.columns[1]) self.log(f"使用信号列: '{signal_col}'") self.log(f"使用数据列: '{data_col}'") # 查找MET列 old_met_cols = self.find_met_columns(df_old, is_old_file=True) new_met_cols = self.find_met_columns(df_new, is_old_file=False) # === 关键修复 1: 标准化用户选择的列 === self.signal_cols = [self.normalize_col_name(col) for col in self.signal_cols] self.data_cols = [self.normalize_col_name(col) for col in self.data_cols] # === 关键修复 2: 改进MET检查 === def check_met_status(row, met_cols): """修正MET状态检查""" for col in met_cols: try: # 直接使用列索引访问,避免get()方法问题 met_value = str(row[col]).strip().upper() if met_value in ['T', 'R', '1', 'Y', 'TRUE']: return True except KeyError: continue return False # === 关键修复 3: 重构层级构建方法 === def build_signal_hierarchy(df, signal_col, data_col): signals = {} current_signal = None for idx, row in df.iterrows(): row_data = row.to_dict() signal_val = str(row_data.get(signal_col, '')).strip() data_val = str(row_data.get(data_col, '')).strip() # 明确信号行判断:信号列有值且数据列为空 if signal_val and not data_val: current_signal = signal_val signals[current_signal] = { 'signal_row': row_data, 'data_items': [] } # 数据行判断:数据列有值 elif data_val and current_signal: signals[current_signal]['data_items'].append(row_data) return signals # 构建层级结构 old_signals = self.build_signal_hierarchy(df_old, signal_col, data_col) new_signals = self.build_signal_hierarchy(df_new, signal_col, data_col) # 存储结果 results = [] # 比较信号级别 for signal_name in set(old_signals.keys()) - set(new_signals.keys()): if check_met_status(old_signals[signal_name]['signal_row'], old_met_cols): results.append({ '信号名': signal_name, '变更类型': '信号删除', '数据名': '', '变更详情': "整个信号被删除" }) for signal_name in set(new_signals.keys()) - set(old_signals.keys()): if check_met_status(new_signals[signal_name]['signal_row'], new_met_cols): results.append({ '信号名': signal_name, '变更类型': '信号新增', '数据名': '', '变更详情': "整个信号被新增" }) # 比较共有信号 for signal_name in set(old_signals.keys()) & set(new_signals.keys()): old_signal = old_signals[signal_name] new_signal = new_signals[signal_name] # 信号级别变更 signal_changes = [] for col in self.signal_cols: norm_col = normalize_col_name(col) old_val = str(old_signal['signal_row'].get(norm_col, '')).strip() new_val = str(new_signal['signal_row'].get(norm_col, '')).strip() # 数值类型特殊处理 if old_val.isdigit() and new_val.isdigit(): if int(old_val) != int(new_val): signal_changes.append(f"{col}: {old_val}→{new_val}") elif old_val != new_val: signal_changes.append(f"{col}: {old_val}→{new_val}") if signal_changes and (check_met_status(old_signal['signal_row'], old_met_cols) or check_met_status(new_signal['signal_row'], new_met_cols)): results.append({ '信号名': signal_name, '变更类型': '信号变更', '数据名': '', '变更详情': "; ".join(signal_changes) }) # 数据级别比较 old_data_map = {str(row[data_col]).strip(): row for row in old_signal['data_items']} new_data_map = {str(row[data_col]).strip(): row for row in new_signal['data_items']} # 检查删除的数据项 ▼ 此处修复:添加变更检测 for data_name in set(old_data_map.keys()) - set(new_data_map.keys()): old_row = old_data_map[data_name] if check_met_status(old_row, old_met_cols): results.append({ '信号名': signal_name, '变更类型': '数据删除', '数据名': data_name, '变更详情': f"数据被删除(满足MET条件)" }) # 检查新增的数据项 ▼ 此处修复:添加变更检测 for data_name in set(new_data_map.keys()) - set(old_data_map.keys()): new_row = new_data_map[data_name] if check_met_status(new_row, new_met_cols): results.append({ '信号名': signal_name, '变更类型': '数据新增', '数据名': data_name, '变更详情': f"数据被新增(满足MET条件)" }) # 关键修复:检查变更的数据项 for data_name in set(old_data_map.keys()) & set(new_data_map.keys()): old_row = old_data_map[data_name] new_row = new_data_map[data_name] # 初始化 data_changes 列表 data_changes = [] # 收集所有数据列的变更 for col in self.data_cols: try: # 直接比较原始值,不进行strip处理 old_val = str(old_row[col]) new_val = str(new_row[col]) if old_val != new_val: # 记录原始值(包含空格) data_changes.append(f"{col}: [{old_val}]→[{new_val}]") except KeyError: continue # 如果列不存在,跳过 # 检查MET条件:旧行或新行满足MET条件 if data_changes and (check_met_status(old_row, old_met_cols) or check_met_status(new_row, new_met_cols)): results.append({ '信号名': signal_name, '变更类型': '数据变更', '数据名': data_name, '变更详情': "; ".join(data_changes) }) # 转为结果DataFrame result_df = pd.DataFrame(results) # 如果结果为空,添加一行提示信息 if result_df.empty: result_df = pd.DataFrame([{ '信号名': '无变更', '变更类型': '无变更', '数据名': '', '变更详情': '未发现满足MET条件的变更' }]) return result_df def generate_report(self, comparison_result): """生成文本格式的详细报告""" report = [] # 检查是否需要生成报告 if comparison_result.empty or (len(comparison_result) == 1 and comparison_result.iloc[0]['信号名'] == '无变更'): return "比较完成,未发现满足MET条件的变更" # 分组处理:按信号名分组 grouped = comparison_result.groupby('信号名') for signal, group in grouped: report.append(f"\n信号: {signal}") # 处理信号级别的变更 signal_changes = group[group['数据名'] == ''] for _, row in signal_changes.iterrows(): if row['变更类型'] == '信号删除': report.append(f" - 信号删除: {row['变更详情']}") elif row['变更类型'] == '信号新增': report.append(f" - 信号新增: {row['变更详情']}") elif row['变更类型'] == '信号变更': report.append(f" - 信号变更: {row['变更详情']}") # 处理数据级别的变更 data_changes = group[group['数据名'] != ''] if not data_changes.empty: # 数据删除 deleted_data = data_changes[data_changes['变更类型'] == '数据删除'] for _, row in deleted_data.iterrows(): report.append(f" - 数据删除: {row['数据名']} - {row['变更详情']}") # 数据新增 added_data = data_changes[data_changes['变更类型'] == '数据新增'] for _, row in added_data.iterrows(): report.append(f" - 数据新增: {row['数据名']} - {row['变更详情']}") # 数据变更 changed_data = data_changes[data_changes['变更类型'] == '数据变更'] for _, row in changed_data.iterrows(): report.append(f" - 数据变更: {row['数据名']} - {row['变更详情']}") # 添加总结信息 summary = f"\n\n总结:\n" summary += f"总变更数: {len(comparison_result)}\n" summary += f"- 信号级别变更: {len(comparison_result[comparison_result['变更类型'].str.contains('信号')])}\n" summary += f"- 数据级别变更: {len(comparison_result[comparison_result['变更类型'].str.contains('数据')])}" report.append(summary) return "\n".join(report) # ===== 主程序入口 ===== if __name__ == "__main__": root = tk.Tk() app = ExcelComparatorApp(root) root.mainloop() 输出数据变更时,让输出的数据名按照读出的顺序进行排列,要不然在查看输出的时候,太乱了
最新发布
09-29
强调:下面的代码是能够运行,现在修改,只是局部修改问题,不要胡乱修改。。。修改时一定要说明在什么类的什么地方,这样不要用户进行修改。找准位置,认真修改,本着用户至上的原则。生成完整代码。。。问题:修改要求:用户原来设计主界面是三栏布局(包括左侧面板、右侧面板和中间区域),中间区域(中间区域分为两个框),现在要求确保动态区严格作为中间区域的第二个(从上往下数),用户在运行代码时就发现:错误的问题是每个模块的名称,标签及条目占了整个界面的下半部分,这是一个严重的错误,,用户现在要求把子类界面改成与主界面一样(也是三栏布局(包括左侧面板、右侧面板和中间区域)。只是每个模块的名称,标签及条目必须完全嵌套在动态区框里面(中间区域往下的第二个框里面),在动态区里面,举例说明,也就是在有四个按钮的框里。。。。原代码# ==================== 主界面修改 ==================== class MainInterface: def init(self, root: Tk, pool: NumberPool): self.root = root self.pool = pool self.left_panel = None self.center_frame = None self.right_panel = None self.core_vars = {} self.pool_vars = {} self.status_var = StringVar() self.dynamic_text = None self.current_module = None self._setup_ui() self._setup_event_handlers() self.module_instances = {} self._init_modules() # 初始化排除号码相关控件 self.exclude_front_entries = [] self.exclude_back_entries = [] self.front_dan_entries = [] self.back_dan_entries = [] # 初始化结果文本控件 self.result_text = None # 初始化排除号码变量 self.exclude_front_var = StringVar() self.exclude_back_var = StringVar() self.recommend_front_var = StringVar() self.recommend_back_var = StringVar() # 初始化模块内容框架 self.dynamic_content = None self.module_content_frame = None self.labels = { 'InputAnalysis_analysis': [ "输入号码:" , "前区:" , "后区:" , "推荐号码:", "前区:" , "后区:", ], 'combination_analysis': [ "前区热号:", "前数字频:", "前频繁推:", "后区热号:", "后数字频:", "后低频推:" ], 'follow_analysis': [ "前推荐多:", "前推荐少:", "后推荐多:", "后推荐少:" ], 'trend_analysis': [ "和值:", "质合比:", "奇偶比:", "断区推荐:", "连号推荐:", "冷热推荐:", "后区热号:", "后区冷号:", "趋势号:" ], 'NumberGeneration_analysis': [ "胆码:" , "前区:" , "后区:", "推荐5注号码:", "1:" "", "2:" "", "3:" "", "4:" "", "5:" "" ], } # 初始化所有模块的条目引用 self.front_dan_entry = None self.back_dan_entry = None self.result_text = None self.exclude_front_entry = None self.exclude_back_entry = None self.front_entry = None self.back_entry = None def _focus_adjacent_entry(self, event, current_idx, offset, area): """移动焦点到相邻的输入框""" entries = self.front_exclude_entries if area == 'front' else self.back_exclude_entries new_idx = current_idx + offset if 0 <= new_idx < len(entries): entries[new_idx].focus_set() def _init_modules(self): """初始化所有分析模块""" modules = { 'input_analysis': InputAnalysisModule, 'combination_analysis': CombinationAnalysisModule, 'follow_analysis': FollowAnalysisModule, 'trend_analysis': TrendAnalysisModule, 'number_generation': NumberGenerationModule } for name, cls in modules.items(): self.module_instances[name] = cls(name) def _setup_event_handlers(self): """初始化事件处理器""" event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.UI_UPDATE, self._handle_ui_update) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _setup_ui(self): self.root.title(f"大乐透智能分析平台 - {GlobalConfig.VERSION}") self.root.geometry("1400x800") # 添加主标题 title_frame = Frame(self.root) title_frame.pack(fill='x', pady=5) Label(title_frame, text="大乐透智能分析平台", font=('微软雅黑', 16, 'bold')).pack(expand=True) # 主容器 - 三栏布局 main_container = PanedWindow(self.root, orient=HORIZONTAL, sashrelief=RAISED, sashwidth=5) main_container.pack(fill='both', expand=True, padx=5, pady=(0, 5)) # 左侧面板 self.left_panel = Frame(main_container, width=200, bg="#eaeaea") main_container.add(self.left_panel, minsize=150, stretch="never") # 中间内容区 self.center_paned = PanedWindow(main_container, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_container.add(self.center_paned, minsize=500, stretch="always") # 右侧面板 self.right_panel = Frame(main_container, width=700, bg="#f5f5f5") main_container.add(self.right_panel, minsize=250, stretch="never") # 初始化各区域 self._setup_left_panel() # 修正方法名 self._setup_center_area() # 修正方法名 self._setup_right_panel() def _setup_left_panel(self): # 修正方法名,添加缺失的"t" """初始化左侧模块按钮区""" module_names = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } for module in GlobalConfig.MODULES: Button( self.left_panel, text=module_names[module], width=18, command=lambda m=module: self._on_module_button_click(m) ).pack(pady=3, padx=5, ipady=3) def _setup_center_area(self): """设置中间区域布局,分为上下两部分""" # 上半部分 - 核心区 self.core_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.core_frame, minsize=150, stretch="never") # 核心区固定高度 # 核心区标题 Label(self.core_frame, text="核心区", font=('微软雅黑', 12, 'bold')).pack(anchor='w', padx=5, pady=2) # 核心数据展示 self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } for label, var_name in [ ("前区:", 'front_area'), ("后区:", 'back_area'), ("前区热号:", 'front_hot'), ("前区冷号:", 'front_cold'), ("后区热号:", 'back_hot'), ("后区冷号:", 'back_cold') ]: frame = Frame(self.core_frame) frame.pack(fill='x', padx=5, pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) entry = Entry(entry_container, textvariable=self.core_vars[var_name], font=('微软雅黑', 10), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) # 下半部分 - 动态区 (严格限制在中间区域下半部分) self.dynamic_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.dynamic_frame, minsize=200, stretch="always") # 动态区可扩展 # 动态区内部容器 - 确保所有模块内容严格嵌套在此框架内 dynamic_container = Frame(self.dynamic_frame) dynamic_container.pack(fill='both', expand=True, padx=5, pady=5) # 动态区标题框架 dynamic_header = Frame(dynamic_container) dynamic_header.pack(fill='x', pady=5) Label(dynamic_header, text="动态区", font=('微软雅黑', 12, 'bold')).pack(side='left') # 按钮框架 btn_frame = Frame(dynamic_header) btn_frame.pack(side='right') Button(btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) Button(btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) Button(btn_frame, text="保存", width=8, command=self._save_dynamic_content).pack(side='left', padx=2) Button(btn_frame, text="刷新", width=8, command=self._refresh_dynamic).pack(side='left', padx=2) # 模块内容容器 - 严格嵌套在动态区内 self.module_content_frame = Frame(dynamic_container) self.module_content_frame.pack(fill='both', expand=True) # 初始化动态区内容为空白 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True) def _on_module_button_click(self, module: str): """模块切换 - 严格在动态区内变化""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module # 清除之前的动态内容 - 仅限动态区 if self.dynamic_content: self.dynamic_content.destroy() # 创建新的动态内容容器 - 严格嵌套在module_content_frame内 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True, padx=5, pady=5) # 顶部模块标签区 - 在动态区内 top_label_frame = Frame(self.dynamic_content) top_label_frame.pack(fill='x', pady=5) module_labels = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } Label(top_label_frame, text=module_labels.get(module, module), font=('微软雅黑', 14, 'bold')).pack() # 内容区容器 - 在动态区内 content_frame = Frame(self.dynamic_content, bd=1, relief='solid') content_frame.pack(fill='both', expand=True, padx=5, pady=5) # 根据模块类型创建特定内容 - 严格在动态区内 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 底部按钮区 - 在动态区内 bottom_frame = Frame(self.dynamic_content) bottom_frame.pack(pady=5, fill='x') Button(bottom_frame, text="运行", command=lambda: self._run_module(module)).pack(side='left', padx=5) Button(bottom_frame, text="清除", command=lambda: self._clear_module_data(module)).pack(side='left', padx=5) Button(bottom_frame, text="保存", command=lambda: self._save_module_data(module)).pack(side='left', padx=5) Button(bottom_frame, text="刷新", command=lambda: self._on_module_renovate(module)).pack(side='right', padx=5) # 模块内容容器 - 这是动态区的核心内容区域 self.module_content_frame = Frame(self.dynamic_frame) self.module_content_frame.pack(fill='both', expand=True, padx=5, pady=5) def _run_current_module(self): """运行当前模块""" if self.current_module: self._run_module(self.current_module) def _clear_dynamic_content(self): """清除动态区内容""" if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_dynamic_content(self): """保存动态区内容""" if hasattr(self, 'result_text') and self.result_text: content = self.result_text.get(1.0, 'end') with open('dynamic_content.txt', 'w', encoding='utf-8') as f: f.write(content) messagebox.showinfo("保存成功", "动态区内容已保存") def _refresh_dynamic(self): """刷新动态区""" if self.current_module: self._on_module_button_click(self.current_module) def _setup_right_panel(self): """设置右侧号码池布局""" # 号码池标题 pool_title_frame = Frame(self.right_panel) pool_title_frame.pack(fill='x', pady=5) Label(pool_title_frame, text="号码池", font=('微软雅黑', 12, 'bold')).pack(anchor='w') # 号码池内容区(添加边框和2px内边距)pool_content = Frame(self.right_panel, bd=1, relief='solid', padx=2, pady=2) pool_content.pack(fill='both', expand=True, padx=5, pady=5) # 创建Canvas和Scrollbar canvas = Canvas(pool_content, highlightthickness=0) scrollbar = Scrollbar(pool_content, orient="vertical", command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 号码池项目 - 优化布局和样式(带2px右边距)for label, var_name, row_id in GlobalConfig.UI_CONFIG: frame = Frame(scrollable_frame) frame.grid(row=row_id, column=0, sticky='ew', padx=0, pady=1) # 移除水平padding # 左侧标签(固定宽度8字符)lbl = Label(frame, text=label, width=8, anchor='w') lbl.pack(side='left', padx=(0, 5)) # 标签右侧留5px间距 # 右侧输入框容器(带2px右边距)entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) var = StringVar() self.pool_vars[var_name] = var entry = Entry(entry_container, textvariable=var, font=('微软雅黑', 9), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 底部按钮区 btn_frame = Frame(self.right_panel) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="整理", width=10, command=self._organize_data).pack(side='left', padx=5) Button(btn_frame, text="冻结", width=10, command=self._freeze_data).pack(side='left', padx=5) Button(btn_frame, text="导出", width=10).pack(side='left', padx=5) def _organize_data(self): """整理号码池数据""" organize_event = Event( event_id=int(time.time()), type=EventType.DATA_ORGANIZE, source='main_ui', target='pool' ) event_center.publish(organize_event) def _freeze_data(self): """冻结号码池数据""" freeze_event = Event( event_id=int(time.time()), type=EventType.DATA_FREEZE, source='main_ui', target='pool' ) event_center.publish(freeze_event) def _on_module_button_click(self, module: str): """完全重构的模块显示方法 - 嵌入主界面动态区""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module # 清除之前的动态内容 if self.dynamic_content: self.dynamic_content.destroy() # 创建模块专属容器 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True, padx=5, pady=5) # 顶部模块标签区 top_label_frame = Frame(self.dynamic_content) top_label_frame.pack(fill='x', pady=5) module_labels = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } Label(top_label_frame, text=module_labels.get(module, module), font=('微软雅黑', 14, 'bold')).pack() # 内容区容器 content_frame = Frame(self.dynamic_content, bd=1, relief='solid') content_frame.pack(fill='both', expand=True, padx=5, pady=5) # 根据模块类型创建特定内容 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 底部按钮区 bottom_frame = Frame(self.dynamic_content) bottom_frame.pack(pady=5, fill='x') Button(bottom_frame, text="运行", command=lambda: self._run_module(module)).pack(side='left', padx=5) Button(bottom_frame, text="清除", command=lambda: self._clear_module_data(module)).pack(side='left', padx=5) Button(bottom_frame, text="保存", command=lambda: self._save_module_data(module)).pack(side='left', padx=5) Button(bottom_frame, text="刷新", command=lambda: self._on_module_renovate(module)).pack(side='right', padx=5) def _create_ui_element(self, parent, label_text): """创建统一的UI元素(带右边距2px)""" frame = Frame(parent) frame.pack(fill='x', pady=2) # 标签固定宽度 Label(frame, text=label_text, width=12, anchor='w').pack(side='left') # 添加容器Frame实现右边距2px entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) return entry_container def _create_input_analysis_content(self, parent: Frame): """创建输入分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 排除号码区 exclude_frame = Frame(content_frame) exclude_frame.pack(fill='x', pady=5) # 排除号码标签 Label(exclude_frame, text="排除号码:", font=('微软雅黑', 10, 'bold')).pack(anchor='w', pady=5) # 前区排除号码 front_exclude_frame = Frame(exclude_frame) front_exclude_frame.pack(fill='x', pady=2) Label(front_exclude_frame, text="前区:", width=5, anchor='w').pack(side='left') self.exclude_front_entry = Entry(front_exclude_frame) self.exclude_front_entry.pack(side='left', fill='x', expand=True) # 后区排除号码 back_exclude_frame = Frame(exclude_frame) back_exclude_frame.pack(fill='x', pady=2) Label(back_exclude_frame, text="后区:", width=5, anchor='w').pack(side='left') self.exclude_back_entry = Entry(back_exclude_frame) self.exclude_back_entry.pack(side='left', fill='x', expand=True) # 号码输入区 input_frame = Frame(content_frame) input_frame.pack(fill='x', pady=5) Label(input_frame, text="输入号码:", font=('微软雅黑', 10, 'bold')).pack(anchor='w', pady=5) # 前区号码 front_frame = Frame(input_frame) front_frame.pack(fill='x', pady=2) Label(front_frame, text="前区:", width=5, anchor='w').pack(side='left') self.front_entry = Entry(front_frame) self.front_entry.pack(side='left', fill='x', expand=True) # 后区号码 back_frame = Frame(input_frame) back_frame.pack(fill='x', pady=2) Label(back_frame, text="后区:", width=5, anchor='w').pack(side='left') self.back_entry = Entry(back_frame) self.back_entry.pack(side='left', fill='x', expand=True) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True, pady=5) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_combination_analysis_content(self, parent: Frame): """创建组合分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels for label in self.labels['combination_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存对控件的引用 var_name = label.replace(':', '').replace(' ', '_') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "front_hot": self.front_hot_entry = entry elif var_name == "front_freq": self.front_freq_entry = entry elif var_name == "front_freq_rec": self.front_freq_rec_entry = entry elif var_name == "back_hot": self.back_hot_entry = entry elif var_name == "back_freq": self.back_freq_entry = entry elif var_name == "back_infreq_rec": self.back_infreq_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_follow_analysis_content(self, parent: Frame): """创建跟随分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['follow_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '_') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "front_more": self.front_more_entry = entry elif var_name == "front_less": self.front_less_entry = entry elif var_name == "back_more": self.back_more_entry = entry elif var_name == "back_less": self.back_less_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_trend_analysis_content(self, parent: Frame): """创建趋势分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['trend_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '_') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "sum_value": self.sum_value_entry = entry elif var_name == "prime_ratio": self.prime_ratio_entry = entry elif var_name == "odd_even_ratio": self.odd_even_ratio_entry = entry elif var_name == "zone_rec": self.zone_rec_entry = entry elif var_name == "consec_rec": self.consec_rec_entry = entry elif var_name == "hot_cold_rec": self.hot_cold_rec_entry = entry elif var_name == "hot_rec": self.hot_rec_entry = entry elif var_name == "cold_rec": self.cold_rec_entry = entry elif var_name == "trend_rec": self.trend_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_number_generation_content(self, parent: Frame): """创建数字生成模块的动态内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 胆码输入区 dan_frame = Frame(content_frame) dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x') Label(front_dan_frame, text="前区胆码:").pack(side='left') self.front_dan_entries = [] for i in range(5): entry = Entry(front_dan_frame, width=3) entry.pack(side='left', padx=2) self.front_dan_entries.append(entry) self.front_dan_entry = self.front_dan_entries[0] # 保存第一个条目引用 # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x') Label(back_dan_frame, text="后区胆码:").pack(side='left') self.back_dan_entries = [] for i in range(5): entry = Entry(back_dan_frame, width=3) entry.pack(side='left', padx=2) self.back_dan_entries.append(entry) self.back_dan_entry = self.back_dan_entries[0] # 保存第一个条目引用 # 生成的号码显示区 generated_frame = Frame(content_frame) generated_frame.pack(fill='x', pady=5) Label(generated_frame, text="生成号码:").pack(anchor='w') self.generated_labels = [] for i in range(1, 6): frame = Frame(generated_frame) frame.pack(fill='x') Label(frame, text=f"{i}.").pack(side='left') label = Label(frame, text="", width=30, anchor='w') label.pack(side='left') self.generated_labels.append(label) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _run_module(self, module: str): """运行模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _generate_recommend_numbers(self, exclude_front: str, exclude_back: str): """生成推荐号码(示例逻辑)""" # 实际应用中应调用分析模块生成推荐号码 # 这里简化为生成随机推荐号码 import random # 前区号码范围1-35 all_front = [str(idx) for idx in range(1, 36)] exclude_front_list = exclude_front.split() if exclude_front else [] available_front = [num for num in all_front if num not in exclude_front_list] # 后区号码范围1-12 all_back = [str(idx) for idx in range(1, 13)] exclude_back_list = exclude_back.split() if exclude_back else [] available_back = [num for num in all_back if num not in exclude_back_list] # 随机选择5个前区号码 if len(available_front) >= 5: recommend_front = random.sample(available_front, 5) else: recommend_front = random.sample(all_front, 5) # 随机选择2个后区号码 if len(available_back) >= 2: recommend_back = random.sample(available_back, 2) else: recommend_back = random.sample(all_back, 2) # 更新推荐号码显示 self.recommend_front_var.set(' '.join(sorted(recommend_front, key=int))) self.recommend_back_var.set(' '.join(sorted(recommend_back, key=int))) # 在结果文本中记录 self.result_text.insert('end', f"生成推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 更新号码池 self._update_pool_with_recommendations(self.recommend_front_var.get(), self.recommend_back_var.get()) def _update_pool_with_recommendations(self, front: str, back: str): """用推荐号码更新号码池""" # 发布事件更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': front, 'back_numbers': back } ) event_center.publish(update_event) # 在结果文本中记录 self.result_text.insert('end', "号码池已更新\n") def _clear_module_data(self, module: str): """清除模块数据""" if module == "input_analysis": if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'recommend_front_var'): self.recommend_front_var.set('') if hasattr(self, 'recommend_back_var'): self.recommend_back_var.set('') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') elif module == "combination_analysis": if hasattr(self, 'front_hot_entry') and self.front_hot_entry: self.front_hot_entry.delete(0, 'end') if hasattr(self, 'front_freq_entry') and self.front_freq_entry: self.front_freq_entry.delete(0, 'end') if hasattr(self, 'front_freq_rec_entry') and self.front_freq_rec_entry: self.front_freq_rec_entry.delete(0, 'end') if hasattr(self, 'back_hot_entry') and self.back_hot_entry: self.back_hot_entry.delete(0, 'end') if hasattr(self, 'back_freq_entry') and self.back_freq_entry: self.back_freq_entry.delete(0, 'end') if hasattr(self, 'back_infreq_rec_entry') and self.back_infreq_rec_entry: self.back_infreq_rec_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_module_data(self, module: str): """保存模块数据""" try: data = {} if module == "input_analysis": data['front'] = self.front_entry.get() data['back'] = self.back_entry.get() data['exclude_front'] = self.exclude_front_entry.get() data['exclude_back'] = self.exclude_back_entry.get() data['recommend_front'] = self.recommend_front_var.get() data['recommend_back'] = self.recommend_back_var.get() data['result'] = self.result_text.get(1.0, 'end') elif module == "combination_analysis": data['front_hot'] = self.front_hot_entry.get() data['front_freq'] = self.front_freq_entry.get() data['front_freq_rec'] = self.front_freq_rec_entry.get() data['back_hot'] = self.back_hot_entry.get() data['back_freq'] = self.back_freq_entry.get() data['back_infreq_rec'] = self.back_infreq_rec_entry.get() data['result'] = self.result_text.get(1.0, 'end') # 其他模块数据收集... filename = f"{module}_data.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"数据已保存到{filename}") except Exception as e: messagebox.showerror("保存失败", str(e)) logging.error(f"保存数据失败: {str(e)}", exc_info=True) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '') exclude_back = event.data.get('exclude_back', '') # 更新排除号码显示 self.exclude_front_entry.delete(0, 'end') self.exclude_front_entry.insert(0, exclude_front) self.exclude_back_entry.delete(0, 'end') self.exclude_back_entry.insert(0, exclude_back) # 在结果文本中记录 self.result_text.insert('end', f"收到排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") def _handle_module_complete(self, event: Event): self.status_var.set(f"{event.source} 模块运行完成") if hasattr(self, 'result_text') and self.result_text: self.result_text.insert('end', f"\n{event.source} 模块已完成分析\n") def _on_module_renovate(self, module: str): """刷新模块""" if module == self.current_module: self._on_module_button_click(module) def _handle_ui_update(self, event: Event): """处理UI更新事件""" if not event.data or 'update_type' not in event.data: return update_type = event.data['update_type'] data = event.data.get('data', {}) # 处理核心变量更新 if update_type == 'organized_data': # 确保所有核心变量已初始化 if not hasattr(self, 'core_vars'): self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } # 更新界面变量 self.core_vars['front_area'].set(str(data.get('front_numbers', []))) self.core_vars['back_area'].set(str(data.get('back_numbers', []))) self.core_vars['front_hot'].set(str(data.get('front_hot', []))) self.core_vars['front_cold'].set(str(data.get('front_cold', []))) self.core_vars['back_hot'].set(str(data.get('back_hot', []))) self.core_vars['back_cold'].set(str(data.get('back_cold', [])))
07-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值