change_colors.h

本文介绍了Windows应用程序中消息处理函数WndProc及对话框过程函数About的声明方式,并展示了资源标识符的定义方法,包括退出命令IDM_EXIT、测试命令IDM_TEST及关于对话框命令IDM_ABOUT。

  name="google_ads_frame" marginwidth="0" marginheight="0" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-5572165936844014&dt=1194442938015&lmt=1194190197&format=336x280_as&output=html&correlator=1194442937843&url=file%3A%2F%2F%2FC%3A%2FDocuments%2520and%2520Settings%2Flhh1%2F%E6%A1%8C%E9%9D%A2%2FCLanguage.htm&color_bg=FFFFFF&color_text=000000&color_link=000000&color_url=FFFFFF&color_border=FFFFFF&ad_type=text&ga_vid=583001034.1194442938&ga_sid=1194442938&ga_hid=1942779085&flash=9&u_h=768&u_w=1024&u_ah=740&u_aw=1024&u_cd=32&u_tz=480&u_java=true" frameborder="0" width="336" scrolling="no" height="280" allowtransparency="allowtransparency"> #define IDM_EXIT           100
#define IDM_TEST           200
#define IDM_ABOUT          301

LRESULT CALLBACK WndProc  (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About    (HWND, UINT, WPARAM, LPARAM);

好吧,我将给出我的原始代码,请你结合你的修改代码进行修改。原始代码如下:from collections import defaultdict import tkinter as tk from tkinter import ttk, messagebox, filedialog, scrolledtext import numpy as np import random import json from datetime import datetime from PIL import Image, ImageTk import os import math from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt import matplotlib.colors as mcolors import matplotlib.patches as patches class ContainerYard: def __init__(self, rows=15, cols=20, max_height=4): self.rows = rows self.cols = cols self.max_height = max_height self.yard = np.zeros((rows, cols, max_height), dtype=object) self.stacking_records = [] self.collapse_records = [] self.exceptions = [] # 集装箱分类区域划分 - 用于不同货物类型 self.type_zones = { "电子产品": (0, 0, rows//3, cols//3), "化工原料": (rows//3, 0, 2*rows//3, cols//3), "生鲜食品": (0, cols//3, rows//3, 2*cols//3), "机械设备": (rows//3, cols//3, 2*rows//3, 2*cols//3), "纺织品": (0, 2*cols//3, rows//3, cols), "其它": (2*rows//3, 0, rows, cols) } # 泊位分配(在洋山港地图对应区域) self.berths = { "泊位1": (2*rows//3, 2*cols//3, rows, cols), "泊位2": (2*rows//3, cols//3, rows, 2*cols//3), "泊位3": (2*rows//3, 0, rows, cols//3), "泊位4": (0, 0, rows//3, cols//3) } def get_position_score(self, row, col, c_type): """计算位置得分,考虑货物类型和离泊位的距离""" # 获取当前货物类型的区域 top, left, bottom, right = self.type_zones.get(c_type, (0, 0, self.rows, self.cols)) # 检查是否在区域内 in_zone = top <= row < bottom and left <= col < right zone_score = 1.0 if in_zone else 0.7 # 计算到中心点的距离(靠近中心的得分更高) center_row = (top + bottom) / 2 center_col = (left + right) / 2 distance = math.sqrt((row - center_row)**2 + (col - center_col)**2) max_distance = math.sqrt(self.rows**2 + self.cols**2) distance_score = 1.0 - distance / max_distance # 组合得分 return zone_score * 0.6 + distance_score * 0.4 def initial_stacking(self, containers): """初始堆叠算法,考虑货物类型的指定区域""" if not containers: return # 按货物类型分类容器 type_containers = defaultdict(list) for container in containers: type_containers[container['type']].append(container) # 分配堆叠位置 for c_type, c_list in type_containers.items(): # 找出该类型可用的堆叠位置 available_spots = [] for row in range(self.rows): for col in range(self.cols): height = self.get_current_height(row, col) if height < self.max_height: score = self.get_position_score(row, col, c_type) available_spots.append((row, col, height, score)) # 按位置得分排序 available_spots.sort(key=lambda x: x[3], reverse=True) # 为每个容器分配位置 for i, container in enumerate(c_list): if i >= len(available_spots): # 没有可用位置了,记录异常 self.exceptions.append({ 'type': '爆仓', 'container': container, 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'message': f"货物类型 {c_type} 没有足够空间存放" }) continue row, col, height, _ = available_spots[i] # 堆叠容器 self.yard[row, col, height] = container container['position'] = (row, col, height) container['stack_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 记录堆叠操作 self.stacking_records.append({ 'operation': 'stack', 'container': container.copy(), 'time': container['stack_time'] }) def collapse_stack(self, row, col, height): """封顶塌陷算法""" collapse_count = 0 collapse_list = [] # 从指定位置上方开始塌陷 for h in range(height, self.max_height): if self.yard[row, col, h] is not None and self.yard[row, col, h] != 0: container = self.yard[row, col, h] collapse_list.append(container) self.yard[row, col, h] = None collapse_count += 1 # 重新堆叠塌陷的容器 if collapse_list: self.stacking_records.append({ 'operation': 'collapse', 'position': (row, col), 'collapsed_containers': [c['id'] for c in collapse_list], 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # 更新塌陷记录 for container in collapse_list: self.collapse_records.append({ 'container_id': container['id'], 'original_position': (row, col, container['position'][2]), 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # 重新堆叠塌陷的容器 self.initial_stacking(collapse_list) return collapse_count def get_current_height(self, row, col): """获取当前堆叠高度""" for height in range(self.max_height): if self.yard[row, col, height] is None or self.yard[row, col, height] == 0: return height return self.max_height def get_yard_status(self, layer=None): """获取堆场状态,可指定层""" if layer is None: status = [] for row in range(self.rows): row_status = [] for col in range(self.cols): height = self.get_current_height(row, col) row_status.append(height) status.append(row_status) return status else: # 获取特定层的信息 layer_status = [] for row in range(self.rows): row_status = [] for col in range(self.cols): if layer < self.get_current_height(row, col): container = self.yard[row, col, layer] row_status.append(container['type'] if container else "空") else: row_status.append("空") layer_status.append(row_status) return layer_status def get_yard_capacity(self): """获取堆场容量表""" capacity = [] for row in range(self.rows): for col in range(self.cols): height = self.get_current_height(row, col) types = [] for h in range(height): if self.yard[row, col, h]: types.append(self.yard[row, col, h]['type']) capacity.append({ 'row': row, 'col': col, 'current_height': height, 'max_height': self.max_height, 'types': types, 'utilization': f"{height}/{self.max_height} ({height/self.max_height*100:.1f}%)" }) return capacity class ContainerInOutSystem: def __init__(self, root): self.root = root self.root.title("洋山港集装箱入库管理系统") self.root.geometry("1280x800") # 初始化数据 self.container_count = 0 self.container_yard = ContainerYard(rows=15, cols=20, max_height=5) self.current_map = None self.map_photo = None self.containers_to_add = [] self.current_layer = 0 self.show_types = False self.map_scale = 1.0 self.map_offset_x = 0 self.map_offset_y = 0 self.map_drag_start = None # 创建界面 self.create_menu() self.create_main_interface() self.create_status_bar() def create_menu(self): """创建菜单栏""" menubar = tk.Menu(self.root) # 文件菜单 file_menu = tk.Menu(menubar, tearoff=0) file_menu.add_command(label="加载地图", command=self.load_map) file_menu.add_command(label="保存记录", command=self.save_records) file_menu.add_command(label="加载记录", command=self.load_records) file_menu.add_separator() file_menu.add_command(label="退出", command=self.root.quit) menubar.add_cascade(label="文件", menu=file_menu) # 视图菜单 view_menu = tk.Menu(menubar, tearoff=0) view_menu.add_command(label="堆场状态", command=self.show_yard_status) view_menu.add_command(label="类型分布", command=self.show_type_distribution) view_menu.add_command(label="堆层切换", command=self.switch_layer_dialog) menubar.add_cascade(label="视图", menu=view_menu) # 记录菜单 record_menu = tk.Menu(menubar, tearoff=0) record_menu.add_command(label="入库记录", command=self.show_inbound_records) record_menu.add_command(label="塌陷记录", command=self.show_collapse_records) record_menu.add_command(label="异常记录", command=self.show_exception_records) menubar.add_cascade(label="记录", menu=record_menu) # 工具菜单 tool_menu = tk.Menu(menubar, tearoff=0) tool_menu.add_command(label="堆场容量表", command=self.show_capacity_table) tool_menu.add_command(label="备忘录", command=self.show_memo_dialog) tool_menu.add_command(label="清除选择", command=self.clear_selections) menubar.add_cascade(label="工具", menu=tool_menu) self.root.config(menu=menubar) def create_main_interface(self): """创建主界面""" main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 左侧地图区域 map_frame = ttk.LabelFrame(main_frame, text="洋山港电子地图") map_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 地图导航工具栏 nav_frame = ttk.Frame(map_frame) nav_frame.pack(fill=tk.X, padx=5, pady=2) ttk.Button(nav_frame, text="放大", command=lambda: self.zoom_map(1.1)).pack(side=tk.LEFT, padx=2) ttk.Button(nav_frame, text="缩小", command=lambda: self.zoom_map(0.9)).pack(side=tk.LEFT, padx=2) ttk.Button(nav_frame, text="还原", command=self.reset_map_view).pack(side=tk.LEFT, padx=2) ttk.Button(nav_frame, text="漫游模式", command=self.toggle_drag_mode).pack(side=tk.LEFT, padx=10) ttk.Label(nav_frame, text="堆层:").pack(side=tk.LEFT, padx=5) self.layer_var = tk.IntVar(value=0) layer_combo = ttk.Combobox(nav_frame, textvariable=self.layer_var, width=5, values=list(range(self.container_yard.max_height))) layer_combo.pack(side=tk.LEFT, padx=2) layer_combo.bind("<<ComboboxSelected>>", self.change_layer) ttk.Checkbutton(nav_frame, text="显示货物类型", variable=tk.BooleanVar(value=self.show_types), command=self.toggle_show_types).pack(side=tk.LEFT, padx=10) # 地图显示区域 self.map_canvas = tk.Canvas(map_frame, bg='#e0e0e0', width=800, height=500) self.map_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 绑定地图事件 self.map_canvas.bind("<MouseWheel>", self.on_mouse_wheel) self.map_canvas.bind("<ButtonPress-1>", self.on_map_drag_start) self.map_canvas.bind("<B1-Motion>", self.on_map_drag) self.map_canvas.bind("<ButtonRelease-1>", self.on_map_drag_end) # 右侧控制面板 control_frame = ttk.Frame(main_frame) control_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=5, pady=5) # 输入框部分 input_frame = ttk.LabelFrame(control_frame, text="集装箱信息") input_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(input_frame, text="泊位:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.berth_combo = ttk.Combobox(input_frame, values=list(self.container_yard.berths.keys()), width=18) self.berth_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5) self.berth_combo.current(0) ttk.Label(input_frame, text="货物类型:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5) self.type_combo = ttk.Combobox(input_frame, values=list(self.container_yard.type_zones.keys()), width=18) self.type_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) self.type_combo.current(0) ttk.Label(input_frame, text="货物数量:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5) self.count_entry = ttk.Entry(input_frame, width=20) self.count_entry.grid(row=2, column=1, sticky=tk.W, padx=5, pady=5) self.count_entry.insert(0, "1") ttk.Label(input_frame, text="储存时间(天):").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5) self.duration_entry = ttk.Entry(input_frame, width=20) self.duration_entry.grid(row=3, column=1, sticky=tk.W, padx=5, pady=5) self.duration_entry.insert(0, "30") # 分配按钮 self.assign_btn = ttk.Button(control_frame, text="开始分配", command=self.assign_containers) self.assign_btn.pack(fill=tk.X, padx=5, pady=10) # 小地图区域 mini_map_frame = ttk.LabelFrame(control_frame, text="堆场概览") mini_map_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.mini_map_figure = plt.Figure(figsize=(3, 3), dpi=80) self.mini_map_canvas = FigureCanvasTkAgg(self.mini_map_figure, mini_map_frame) self.mini_map_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) ax = self.mini_map_figure.add_subplot(111) ax.set_title("堆场概览") self.update_mini_map(ax) # 日志框 log_frame = ttk.LabelFrame(control_frame, text="操作日志") log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.log_text = scrolledtext.ScrolledText(log_frame, height=10, width=40) self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.log_text.config(state=tk.DISABLED) def create_status_bar(self): """创建状态栏""" self.status_bar = ttk.Frame(self.root) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=5) self.status_label = ttk.Label(self.status_bar, text="就绪 | 比例: 100% | 堆层: 0 | 位置: (0, 0)") self.status_label.pack(side=tk.LEFT) self.container_count_label = ttk.Label(self.status_bar, text="集装箱总数: 0") self.container_count_label.pack(side=tk.RIGHT, padx=10) def update_status(self, message): """更新状态栏""" self.status_label.config(text=f"{message} | 比例: {self.map_scale*100:.0f}% | 堆层: {self.current_layer} | 位置: ({self.map_offset_x}, {self.map_offset_y})") self.status_label.update_idletasks() def log_message(self, message): """记录日志消息""" self.log_text.config(state=tk.NORMAL) timestamp = datetime.now().strftime("%H:%M:%S") self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") self.log_text.config(state=tk.DISABLED) self.log_text.see(tk.END) self.update_status(message) def load_map(self): """加载洋山港地图数据""" filetypes = [("地图文件", "*.png *.jpg *.jpeg"), ("所有文件", "*.*")] filename = filedialog.askopenfilename(title="选择洋山港地图文件", filetypes=filetypes) if filename: try: # 加载地图图片 self.current_map = Image.open(filename) self.map_photo = ImageTk.PhotoImage(self.current_map) # 绘制地图 self.draw_map() # 绘制堆场区域和泊位 self.draw_yard_zones() self.log_message(f"地图加载成功: {os.path.basename(filename)}") self.log_message("堆场区域和泊位已标注") except Exception as e: messagebox.showerror("错误", f"加载地图失败: {str(e)}") self.log_message(f"地图加载失败: {str(e)}") def draw_map(self): """绘制地图到画布""" if not self.map_photo: return # 清空画布 self.map_canvas.delete("all") # 计算缩放和偏移 scaled_width = int(self.current_map.width * self.map_scale) scaled_height = int(self.current_map.height * self.map_scale) # 创建缩放后的图片 resized_img = self.current_map.resize((scaled_width, scaled_height), Image.LANCZOS) self.map_photo = ImageTk.PhotoImage(resized_img) # 绘制地图 self.map_canvas.create_image( self.map_offset_x, self.map_offset_y, anchor=tk.NW, image=self.map_photo, tags="map" ) # 更新堆场状态覆盖 self.draw_yard_overlay() def draw_yard_zones(self): """在堆场上绘制不同类型区域的边框""" if not self.map_photo: return # 获取地图的缩放比例 scale_x = self.map_canvas.winfo_width() / self.container_yard.cols scale_y = self.map_canvas.winfo_height() / self.container_yard.rows # 绘制货物类型区域 colors = { "电子产品": "blue", "化工原料": "green", "生鲜食品": "yellow", "机械设备": "orange", "纺织品": "purple", "其它": "gray" } for zone_name, (top, left, bottom, right) in self.container_yard.type_zones.items(): # 计算屏幕坐标 x1 = left * scale_x y1 = top * scale_y x2 = right * scale_x y2 = bottom * scale_y # 绘制矩形 self.map_canvas.create_rectangle( x1, y1, x2, y2, outline=colors[zone_name], width=2, dash=(4, 4), tags="zone" ) # 添加标签 self.map_canvas.create_text( (x1+x2)/2, (y1+y2)/2, text=zone_name, fill=colors[zone_name], font=("Arial", 10, "bold"), tags="zone" ) # 绘制泊位 berth_colors = ["red", "cyan", "magenta", "brown"] for i, (berth_name, (top, left, bottom, right)) in enumerate(self.container_yard.berths.items()): # 计算屏幕坐标 x1 = left * scale_x y1 = top * scale_y x2 = right * scale_x y2 = bottom * scale_y # 绘制矩形 self.map_canvas.create_rectangle( x1, y1, x2, y2, outline=berth_colors[i], width=3, tags="berth" ) # 添加泊位标签 self.map_canvas.create_text( (x1+x2)/2, (y1+y2)/2, text=berth_name, fill=berth_colors[i], font=("Arial", 12, "bold"), tags="berth" ) def draw_yard_overlay(self): """绘制堆场状态覆盖层,显示当前层集装箱位置""" if not self.map_photo: return # 获取地图的缩放比例 canvas_width = self.map_canvas.winfo_width() canvas_height = self.map_canvas.winfo_height() scale_x = canvas_width / self.container_yard.cols scale_y = canvas_height / self.container_yard.rows # 清除之前的覆盖层 self.map_canvas.delete("container") # 获取当前层状态 layer_status = self.container_yard.get_yard_status(self.current_layer) # 定义颜色映射 type_colors = { "电子产品": "blue", "化工原料": "green", "生鲜食品": "#FFC000", # 黄色 "机械设备": "orange", "纺织品": "purple", "其它": "gray", "空": "#E0E0E0" # 浅灰色 } # 绘制集装箱位置 for row in range(self.container_yard.rows): for col in range(self.container_yard.cols): if layer_status[row][col] != "空": # 计算屏幕坐标 x1 = col * scale_x y1 = row * scale_y x2 = (col+1) * scale_x y2 = (row+1) * scale_y # 绘制矩形 container_type = layer_status[row][col] fill_color = type_colors.get(container_type, "gray") self.map_canvas.create_rectangle( x1, y1, x2, y2, outline="black", fill=fill_color, width=1, stipple="gray25", # 半透明效果 tags="container" ) # 如果显示货物类型,添加文本标签 if self.show_types: self.map_canvas.create_text( (x1+x2)/2, (y1+y2)/2, text=container_type[:2], fill="black" if fill_color == "#FFC000" else "white", font=("Arial", 8), tags="container" ) def update_mini_map(self, ax): """更新小地图显示""" ax.clear() # 获取堆场状态 status = self.container_yard.get_yard_status() data = np.array(status) # 创建颜色映射 cmap = plt.cm.get_cmap('viridis') norm = mcolors.Normalize(vmin=0, vmax=self.container_yard.max_height) # 绘制热力图 im = ax.imshow(data, cmap=cmap, norm=norm, aspect='auto') # 添加标题和颜色条 ax.set_title("堆场利用率概览") plt.colorbar(im, ax=ax) self.mini_map_canvas.draw() def zoom_map(self, factor): """缩放地图""" if not self.current_map: return self.map_scale *= factor self.map_scale = max(0.1, min(self.map_scale, 3.0)) # 限制缩放范围 self.draw_map() self.log_message(f"地图缩放: {self.map_scale*100:.0f}%") def reset_map_view(self): """重置地图视图""" self.map_scale = 1.0 self.map_offset_x = 0 self.map_offset_y = 0 self.draw_map() self.log_message("地图视图已重置") def toggle_drag_mode(self): """切换漫游模式""" if self.map_canvas["cursor"] == "fleur": self.map_canvas.config(cursor="") self.log_message("漫游模式已关闭") else: self.map_canvas.config(cursor="fleur") self.log_message("漫游模式已开启") def on_mouse_wheel(self, event): """鼠标滚轮事件处理""" if not self.current_map: return factor = 1.1 if event.delta > 0 else 0.9 self.zoom_map(factor) def on_map_drag_start(self, event): """开始地图拖动""" self.map_drag_start = (event.x, event.y) def on_map_drag(self, event): """地图拖动中""" if self.map_drag_start and self.map_canvas["cursor"] == "fleur": dx = event.x - self.map_drag_start[0] dy = event.y - self.map_drag_start[1] self.map_offset_x += dx self.map_offset_y += dy self.map_drag_start = (event.x, event.y) self.draw_map() def on_map_drag_end(self, event): """结束地图拖动""" self.map_drag_start = None def change_layer(self, event=None): """改变当前显示的堆层""" self.current_layer = self.layer_var.get() self.draw_yard_overlay() self.log_message(f"切换到堆层: {self.current_layer}") def toggle_show_types(self): """切换是否显示货物类型""" self.show_types = not self.show_types self.draw_yard_overlay() state = "开启" if self.show_types else "关闭" self.log_message(f"货物类型显示已{state}") def clear_selections(self): """清除所有选择标记""" self.map_canvas.delete("selection") self.log_message("选择标记已清除") def validate_input(self): """验证输入数据""" errors = [] # 验证泊位 berth = self.berth_combo.get().strip() if not berth or berth not in self.container_yard.berths: errors.append("请选择有效的泊位") # 验证货物类型 c_type = self.type_combo.get().strip() if not c_type or c_type not in self.container_yard.type_zones: errors.append("请选择有效的货物类型") # 验证货物数量 count = self.count_entry.get().strip() if not count.isdigit() or int(count) <= 0 or int(count) > 100: errors.append("货物数量必须是1-100之间的整数") # 验证储存时间 duration = self.duration_entry.get().strip() if not duration.isdigit() or int(duration) <= 0 or int(duration) > 365: errors.append("储存时间必须是1-365之间的整数") return errors def assign_containers(self): """分配集装箱""" # 验证输入 errors = self.validate_input() if errors: messagebox.showwarning("输入错误", "\n".join(errors)) for error in errors: self.log_message(f"输入错误: {error}") return # 获取输入值 berth = self.berth_combo.get().strip() c_type = self.type_combo.get().strip() count = int(self.count_entry.get().strip()) duration = int(self.duration_entry.get().strip()) # 生成集装箱数据 containers = [] for i in range(count): self.container_count += 1 container = { 'id': f"C{self.container_count:04d}", 'berth': berth, 'type': c_type, 'size': random.choice(['20ft', '40ft']), 'weight': random.randint(3, 30), 'owner': random.choice(['公司A', '公司B', '公司C']), 'destination': random.choice(['上海', '深圳', '新加坡', '洛杉矶']), 'duration': duration, 'position': None, 'stack_time': None } containers.append(container) self.containers_to_add.append(container) # 更新UI self.log_message(f"已添加{count}个{c_type}集装箱到泊位{berth}") # 创建并开始分配过程 self.start_allocation_process(containers) def start_allocation_process(self, containers): """开始分配过程动画""" # 禁用按钮 self.assign_btn.config(state=tk.DISABLED) # 在后台进行分配 self.root.after(100, self.perform_allocation, containers) def perform_allocation(self, containers): """执行分配过程""" try: # 初始堆叠 self.log_message("正在进行初始堆叠...") self.container_yard.initial_stacking(containers) # 检查是否有爆仓异常 if self.container_yard.exceptions: for exception in self.container_yard.exceptions: self.log_message(f"异常: {exception['message']}") self.container_yard.exceptions.clear() # 更新小地图 ax = self.mini_map_figure.add_subplot(111) self.update_mini_map(ax) # 更新主地图 self.draw_map() self.draw_yard_overlay() # 更新统计信息 self.container_count_label.config(text=f"集装箱总数: {self.container_count}") # 显示成功消息 self.log_message(f"成功分配{len(containers)}个集装箱到堆场") # 记录完成操作 for container in containers: if container['position'] is not None: position = f"{container['position'][0]},{container['position'][1]},{container['position'][2]}" self.log_message(f"集装箱 {container['id']} 已堆叠在位置 {position}") # 启用按钮 self.assign_btn.config(state=tk.NORMAL) except Exception as e: self.log_message(f"分配过程中发生错误: {str(e)}") messagebox.showerror("分配错误", f"分配过程中发生错误: {str(e)}") self.assign_btn.config(state=tk.NORMAL) def show_yard_status(self): """显示堆场状态""" status = self.container_yard.get_yard_status() status_text = "堆场状态 (行/列):\n " + "\t".join(str(i) for i in range(self.container_yard.cols)) + "\n" for i, row in enumerate(status): status_text += f"{i:2d} [\t" + "\t".join(str(height) for height in row) + "]\n" self.show_info_dialog("堆场状态", status_text) def show_type_distribution(self): """显示货物类型分布""" type_distribution = self.container_yard.get_container_count_by_type() total = sum(type_distribution.values()) # 创建图表 fig = plt.Figure(figsize=(7, 5), dpi=80) ax = fig.add_subplot(111) types = list(type_distribution.keys()) counts = list(type_distribution.values()) if types: # 生成不同颜色 colors = plt.cm.tab10(range(len(types))) ax.bar(types, counts, color=colors) # 添加数据标签 for i, v in enumerate(counts): ax.text(i, v + 0.5, str(v), ha='center', fontsize=10) ax.set_title(f"货物类型分布 (总数: {total})") ax.set_ylabel("数量") ax.set_xlabel("货物类型") ax.tick_params(axis='x', rotation=15) else: ax.text(0.5, 0.5, "无货物数据", fontsize=15, ha='center', va='center', color='gray') ax.set_title("货物类型分布") # 在Tkinter中显示图表 self.show_figure_dialog("货物类型分布", fig) def switch_layer_dialog(self): """切换堆层对话框""" dialog = tk.Toplevel(self.root) dialog.title("堆层切换") dialog.geometry("300x200") ttk.Label(dialog, text="请选择要显示的堆层:").pack(pady=10) layer_var = tk.IntVar(value=self.current_layer) layer_combo = ttk.Combobox(dialog, textvariable=layer_var, width=10, values=list(range(self.container_yard.max_height))) layer_combo.pack(pady=10) layer_combo.current(self.current_layer) def apply_layer(): self.current_layer = layer_var.get() self.layer_var.set(self.current_layer) self.draw_yard_overlay() dialog.destroy() self.log_message(f"切换到堆层: {self.current_layer}") ttk.Button(dialog, text="确认", command=apply_layer).pack(pady=20) def show_inbound_records(self): """显示入库记录""" if not self.container_yard.stacking_records: self.show_info_dialog("入库记录", "没有入库记录") return records_text = "入库记录:\n\n" for record in self.container_yard.stacking_records: if record['operation'] == 'stack': c = record['container'] position = c['position'] pos_str = f"{position[0]},{position[1]},{position[2]}" if position else "未分配" records_text += f"集装箱 {c['id']} - {c['type']} (来自泊位{c['berth']})\n" records_text += f"位置: {pos_str}, 时间: {record['time']}\n" records_text += f"尺寸: {c['size']}, 重量: {c['weight']}吨, 所有者: {c['owner']}\n" records_text += f"目的地: {c['destination']}, 储存时间: {c['duration']}天\n\n" else: # collapse records_text += f"塌陷操作 - 位置: {record['position']}\n" records_text += f"受影响的集装箱: {', '.join(record['collapsed_containers'])}\n" records_text += f"时间: {record['time']}\n\n" self.show_info_dialog("入库记录", records_text) def show_collapse_records(self): """显示塌陷记录""" if not self.container_yard.collapse_records: self.show_info_dialog("塌陷记录", "没有塌陷记录") return records_text = "塌陷记录:\n\n" for record in self.container_yard.collapse_records: original_pos = f"{record['original_position'][0]},{record['original_position'][1]},{record['original_position'][2]}" records_text += f"集装箱 {record['container_id']}\n" records_text += f"原位置: {original_pos}, 时间: {record['time']}\n\n" self.show_info_dialog("塌陷记录", records_text) def show_exception_records(self): """显示异常记录""" if not self.container_yard.exceptions and not [r for r in self.container_yard.stacking_records if r['operation'] == 'exception']: self.show_info_dialog("异常记录", "没有异常记录") return records_text = "异常记录:\n\n" # 处理当前异常 for exception in self.container_yard.exceptions: records_text += f"时间: {exception['time']}\n" records_text += f"类型: {exception['type']}\n" records_text += f"内容: {exception['message']}\n" if 'container' in exception: c = exception['container'] records_text += f"相关集装箱: {c['id']} (类型: {c['type']}, 泊位: {c['berth']})\n" records_text += "\n" self.show_info_dialog("异常记录", records_text) def show_memo_dialog(self): """显示备忘录对话框""" dialog = tk.Toplevel(self.root) dialog.title("备忘录") dialog.geometry("500x400") ttk.Label(dialog, text="异常情况记录:", font=("Arial", 10, "bold")).pack(pady=5, anchor=tk.W, padx=10) # 异常记录文本框 memo_frame = ttk.Frame(dialog) memo_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) memo_text = scrolledtext.ScrolledText(memo_frame, wrap=tk.WORD) memo_text.pack(fill=tk.BOTH, expand=True) # 添加现有异常记录 for exception in self.container_yard.exceptions: memo_text.insert(tk.END, f"[{exception['time']}] {exception['message']}\n") # 添加新异常按钮 def add_exception(): exception_type = exception_var.get() description = description_entry.get("1.0", tk.END).strip() if not description: messagebox.showwarning("输入错误", "请输入异常描述") return new_exception = { 'type': exception_type, 'message': description, 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") } self.container_yard.exceptions.append(new_exception) memo_text.insert(tk.END, f"[{new_exception['time']}] {description}\n") self.log_message(f"已记录异常: {exception_type} - {description[:30]}...") description_entry.delete("1.0", tk.END) exception_frame = ttk.Frame(dialog) exception_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(exception_frame, text="异常类型:").pack(side=tk.LEFT, padx=5) exception_var = tk.StringVar() exception_combo = ttk.Combobox(exception_frame, textvariable=exception_var, width=12, values=["爆仓", "设备故障", "操作失误", "货物异常", "其他"]) exception_combo.pack(side=tk.LEFT, padx=5) exception_combo.current(0) ttk.Label(exception_frame, text="描述:").pack(side=tk.LEFT, padx=5) description_entry = tk.Text(exception_frame, height=3, width=30) description_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) ttk.Button(exception_frame, text="添加记录", command=add_exception).pack(side=tk.RIGHT, padx=5) ttk.Button(dialog, text="关闭", command=dialog.destroy).pack(pady=10) def show_capacity_table(self): """显示堆场容量表""" capacity_data = self.container_yard.get_yard_capacity() dialog = tk.Toplevel(self.root) dialog.title("堆场容量表") dialog.geometry("800x600") # 创建表格 tree_frame = ttk.Frame(dialog) tree_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) tree = ttk.Treeview(tree_frame, columns=('row', 'col', 'utilization', 'types'), show='headings') # 设置列 tree.heading('row', text='行') tree.heading('col', text='列') tree.heading('utilization', text='利用率') tree.heading('types', text='货物类型分布') tree.column('row', width=50, anchor='center') tree.column('col', width=50, anchor='center') tree.column('utilization', width=100, anchor='center') tree.column('types', width=500, anchor='w') # 添加数据 for item in capacity_data: types_str = ", ".join(item['types']) tree.insert('', 'end', values=( item['row'], item['col'], item['utilization'], types_str )) # 添加滚动条 scrollbar = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=tree.yview) tree.configure(yscroll=scrollbar.set) # 布局 tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 统计信息 total_slots = self.container_yard.rows * self.container_yard.cols * self.container_yard.max_height used_slots = sum(len(item['types']) for item in capacity_data) utilization = used_slots / total_slots * 100 stats_frame = ttk.Frame(dialog) stats_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(stats_frame, text=f"总容量: {total_slots} | 已使用: {used_slots} | 利用率: {utilization:.1f}%", font=("Arial", 10, "bold")).pack(pady=5) ttk.Button(dialog, text="关闭", command=dialog.destroy).pack(pady=10) def show_info_dialog(self, title, content): """显示信息对话框""" dialog = tk.Toplevel(self.root) dialog.title(title) dialog.geometry("600x400") text = scrolledtext.ScrolledText(dialog, wrap=tk.WORD) text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) text.insert(tk.END, content) text.config(state=tk.DISABLED) btn = ttk.Button(dialog, text="关闭", command=dialog.destroy) btn.pack(pady=10) def show_figure_dialog(self, title, figure): """显示图表对话框""" dialog = tk.Toplevel(self.root) dialog.title(title) dialog.geometry("800x600") canvas = FigureCanvasTkAgg(figure, dialog) canvas.draw() canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) btn = ttk.Button(dialog, text="关闭", command=dialog.destroy) btn.pack(pady=10) def save_records(self): """保存记录到文件""" try: filetypes = [("JSON文件", "*.json"), ("所有文件", "*.*")] filename = filedialog.asksaveasfilename( title="保存记录", filetypes=filetypes, defaultextension=".json") if filename: data = { 'container_count': self.container_count, 'stacking_records': self.container_yard.stacking_records, 'collapse_records': self.container_yard.collapse_records, 'exceptions': self.container_yard.exceptions, 'containers_to_add': self.containers_to_add, 'type_zones': self.container_yard.type_zones, 'berths': self.container_yard.berths } with open(filename, 'w') as f: json.dump(data, f, indent=2) self.log_message(f"记录已保存到 {filename}") except Exception as e: messagebox.showerror("保存错误", f"保存记录失败: {str(e)}") self.log_message(f"保存记录失败: {str(e)}") def load_records(self): """从文件加载记录""" try: filetypes = [("JSON文件", "*.json"), ("所有文件", "*.*")] filename = filedialog.askopenfilename( title="加载记录", filetypes=filetypes) if filename: with open(filename, 'r') as f: data = json.load(f) # 恢复数据 self.container_count = data.get('container_count', 0) self.container_yard.stacking_records = data.get('stacking_records', []) self.container_yard.collapse_records = data.get('collapse_records', []) self.container_yard.exceptions = data.get('exceptions', []) self.containers_to_add = data.get('containers_to_add', []) self.container_yard.type_zones = data.get('type_zones', self.container_yard.type_zones) self.container_yard.berths = data.get('berths', self.container_yard.berths) # 重建堆场 self.container_yard = ContainerYard(rows=15, cols=20, max_height=5) self.container_yard.stacking_records = data.get('stacking_records', []) self.container_yard.collapse_records = data.get('collapse_records', []) self.container_yard.exceptions = data.get('exceptions', []) self.container_yard.type_zones = data.get('type_zones', self.container_yard.type_zones) self.container_yard.berths = data.get('berths', self.container_yard.berths) # 重建堆场状态 for record in self.container_yard.stacking_records: if record['operation'] == 'stack': container = record['container'] if container['position']: row, col, height = container['position'] self.container_yard.yard[row, col, height] = container # 更新UI self.container_count_label.config(text=f"集装箱总数: {self.container_count}") # 更新小地图 ax = self.mini_map_figure.add_subplot(111) self.update_mini_map(ax) # 更新主地图 self.draw_map() self.log_message(f"记录已从 {filename} 加载") except Exception as e: messagebox.showerror("加载错误", f"加载记录失败: {str(e)}") self.log_message(f"加载记录失败: {str(e)}") if __name__ == "__main__": root = tk.Tk() app = ContainerInOutSystem(root) root.mainloop()
07-29
用vsc远程树莓派5代码中:import threading from idlelib.iomenu import encoding from PyQt6.QtWidgets import QLabel, QLineEdit, QTextEdit, QComboBox, QPushButton from PyQt6.QtWidgets import QApplication, QWidget from PyQt6.QtCore import Qt, QEvent from PyQt6 import uic import sys import cv2 from PyQt6.QtGui import QImage, QPixmap import time import line import stm32 import numpy as np from PyQt6.QtCore import pyqtSignal button_activate = "" ser_out = bytes([]) ser_sent = 0 class MyWindow: def __init__(self, mode_cross, mode_weight, cap, ui_path, ser_32): self.app = QApplication(sys.argv) self.my_ui: QWidget = uic.loadUi(ui_path) self.state = "当前: 调试模式" self.move_num = "" # 实际使用的顺序编号 self.QR_Enable = True self.cap = cap self.mode_cross = mode_cross self.mode_weight = mode_weight # 实列化32, 链接qr和cross信号 self.worker = stm32.Stm32(ser_32) self.worker.update_qr_signal.connect(self.qr_update) self.worker.update_cross_signal.connect(self.cross_update) self.worker.update_weight_signal.connect(self.weight_update) self.worker.update_serial_signal.connect(self.state_serial) self.worker.update_line_signal.connect(self.line_update) # 加载控件: 关闭按钮 self.pushButton_Esc: QPushButton = self.my_ui.pushButton_Esc self.pushButton_Esc.clicked.connect(self.__button_esc) # 加载控件: 切换模式按钮 self.pushButton_Change: QPushButton = self.my_ui.pushButton_Change self.pushButton_Change.clicked.connect(self.__button_change) # 加载控件: 运行按钮 self.pushButton_Start: QPushButton = self.my_ui.pushButton_Start self.pushButton_Start.clicked.connect(self.__button_start) # 加载控件: 文字显示模块 self.textEdit: QTextEdit = self.my_ui.textEdit self.textEdit.textChanged.connect(self.handle_text_changed) # 加载图像显示框label self.label_1: QLabel = self.my_ui.label_1 self.label_2: QLabel = self.my_ui.label_2 self.label_3: QLabel = self.my_ui.label_3 # 加载控件: qr启动按钮 self.pushButton_QR: QPushButton = self.my_ui.pushButton_QR self.pushButton_QR.clicked.connect(self.__button_qr) # 加载控件: ring启动按钮 self.pushButton_Ring: QPushButton = self.my_ui.pushButton_Ring self.pushButton_Ring.clicked.connect(self.__button_ring) # 加载控件: weight启动按钮 self.pushButton_Weight: QPushButton = self.my_ui.pushButton_Weight self.pushButton_Weight.clicked.connect(self.__button_weight) # 加载控件: line启动按钮 self.pushButton_Line: QPushButton = self.my_ui.pushButton_Line self.pushButton_Line.clicked.connect(self.__button_line) # 加载控件: 串口信息显示框 self.textEdit_serial: QTextEdit = self.my_ui.textEdit_serial self.textEdit.setFocus() def show_frame(self, lab_num, rgb_img): if lab_num == 2: show_lab = self.label_2 elif lab_num == 3: show_lab = self.label_3 else: show_lab = self.label_1 h, w, ch = rgb_img.shape window_w = int(show_lab.geometry().width()) window_h = int(show_lab.geometry().height()) if window_w != w or window_h != h: rgb_img = cv2.resize(rgb_img, (window_w, window_h)) qt_image = QImage(rgb_img.data, window_w, window_h, QImage.Format.Format_RGB888) qt_pixmap = QPixmap.fromImage(qt_image) show_lab.setPixmap( qt_pixmap.scaled(400, 300, Qt.AspectRatioMode.KeepAspectRatio)) def qr_update(self): self.textEdit.setFocus() self.QR_Enable = True def cross_update(self): global ser_out, ser_sent img, yes, strs = self.mode_scan(self.mode_cross) if yes: ser_out = bytes(f"{chr(13)}{chr(4)}" + strs[:-1], encoding='utf8') else: ser_out = bytes(f"{chr(2)}{chr(4)}", encoding='utf8') ser_sent = 1 self.show_frame(1, cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) def weight_update(self): global ser_out, ser_sent img, yes, strs = self.mode_scan(self.mode_weight) if yes: ser_out = bytes(f"{chr(14)}{chr(6)}" + strs, encoding='utf8') else: ser_out = bytes(f"{chr(2)}{chr(6)}", encoding='utf8') ser_sent = 1 self.show_frame(2, cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) def line_update(self): global ser_out, ser_sent img0 = self.cap.read()[1] angle = line.detect_slope(img0) print(f"line angle == {angle}") send_bytes = line.float_to_bytes(angle) ser_out = bytes(f"{chr(6)}{chr(10)}", encoding='utf8') + send_bytes ser_sent = 1 self.show_frame(3, cv2.cvtColor(img0, cv2.COLOR_BGR2RGB)) def mode_scan(self, mode): img0 = self.cap.read()[1] det_boxes, scores, ids = mode.infer_img(img0, 0.4, 0.5) if len(det_boxes) > 0: # 结果绘图 for box, score, id1 in zip(det_boxes, scores, ids): label = '%s:%.2f' % (mode.dic_labels[id1], score) box = box.astype(np.int32) mode.plot_one_box(box.astype(np.int32), img0, (0, 255, 0), in_label=label, line_thickness=1) # 遍历数组以得到绿色的作为best_idx green_indices = [i for i, id1 in enumerate(ids) if mode.dic_labels[id1] == 'green'] if green_indices: best_idx = green_indices[0] # 选择第一个绿色物体 best_box = det_boxes[best_idx] else: # 没找到绿色的则选择置信度最高的 best_idx = np.argmax(scores) best_box = det_boxes[best_idx] # 计算中心坐标 x_ = int((best_box[0] + best_box[2]) / 2) y_ = int((best_box[1] + best_box[3]) / 2) colors = ['r', 'g', 'b'] sent=f"x={str(x_).zfill(3)},y={str(y_).zfill(3)}{colors[ids[best_idx]]}" return img0, True, sent else: return img0, False, f"" def handle_text_changed(self): global ser_out, ser_sent all_text = self.textEdit.toPlainText() # 获取QTextEdit中的所有文本 if '\n' in all_text: # 检查是否包含Enter键 lines = all_text.split('\n') last_line = lines[-2] if len(lines) > 1 else lines[-1] # 设置QTextEdit的文本 self.textEdit.blockSignals(True) # 暂时阻止信号,以避免递归调用 if self.QR_Enable: # 读取成功 self.textEdit.setPlainText(last_line) self.move_num = last_line self.QR_Enable = False send_str = f"{chr(9)}{chr(2)}" + last_line ser_out = bytes(send_str, encoding='utf8') ser_sent = 1 else: self.textEdit.setPlainText(self.move_num) self.textEdit.blockSignals(False) # 恢复信号 def run(self): self.worker.start_run() self.my_ui.show() # 显示窗口 #self.my_ui.showFullScreen() # 全屏 self.app.exec() # 启动 def __button_esc(self): sys.exit() def __button_start(self): self.textEdit.setFocus() def __button_change(self): if self.state == "当前: 调试模式": self.state = "当前: 运行模式" self.pushButton_Change.setText(self.state) else: self.state = "当前: 调试模式" self.pushButton_Change.setText(self.state) def __button_qr(self): global button_activate button_activate = "QR_ack" def __button_ring(self): global button_activate button_activate = "Cross_ack" def __button_weight(self): global button_activate button_activate = "Weight_ack" def __button_line(self): global button_activate button_activate = "Line_ack" def state_serial(self, state): self.textEdit_serial.setText(state) (myenv) pi@raspberrypi:~ $ /home/pi/myenv/bin/python /home/pi/gong_xun_python-main/qt.py Traceback (most recent call last): File "/home/pi/gong_xun_python-main/qt.py", line 2, in <module> from idlelib.iomenu import encoding ModuleNotFoundError: No module named 'idlelib'
最新发布
10-10
# -*- coding import tkinter as tk from tkinter import ttk, messagebox from PIL import Image, ImageTk, ImageDraw, ImageFont import vlc import os import cv2 import json import time import threading import queue import random import subprocess OUT_DIR = "./output" class EmployeeClockSystem: def __init__(self, root): self.root = root self.root.title("员工工牌识别打卡系统") self.root.geometry("1200x700") self.root.configure(bg="#f0f0f0") # 设置输出目录 self.OUT_DIR = "./output" os.makedirs(self.OUT_DIR, exist_ok=True) # 创建样式 self.style = ttk.Style() self.style.configure("Title.TLabel", font=("微软雅黑", 18, "bold"), foreground="#2c3e50") self.style.configure("Subtitle.TLabel", font=("微软雅黑", 14), foreground="#34495e") self.style.configure("Info.TLabel", font=("微软雅黑", 12), foreground="#2c3e50") self.style.configure("Card.TFrame", background="#ffffff", borderwidth=1, relief="raised", padding=10) self.style.configure("Control.TFrame", background="#e0e0e0", borderwidth=1, relief="sunken", padding=10) # 主布局框架 - 使用PanedWindow实现可调整的分割 main_paned = tk.PanedWindow(root, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashwidth=4) main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 左侧视频区域 (50%) left_frame = ttk.Frame(main_paned) main_paned.add(left_frame, stretch="always") # 右侧员工信息区域 (50%) right_frame = ttk.Frame(main_paned) main_paned.add(right_frame, stretch="always") # 视频流标题 ttk.Label(left_frame, text="实时视频监控", style="Title.TLabel").pack(pady=(0, 10), anchor=tk.W, padx=10) # 视频显示区域 video_card = ttk.Frame(left_frame, style="Card.TFrame") video_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) self.video_container = ttk.Frame(video_card) self.video_container.pack(fill=tk.BOTH, expand=True) # 视频控制面板 control_frame = ttk.Frame(left_frame, style="Control.TFrame") control_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) # URL输入框 ttk.Label(control_frame, text="RTSP地址:").pack(side=tk.LEFT, padx=(0, 5)) self.url_entry = ttk.Entry(control_frame, width=40) self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10)) self.url_entry.insert(0, "rtsp://tapocxy:123456@192.168.137.100/stream1") # 连接按钮 self.connect_button = ttk.Button(control_frame, text="启动监控", command=self.toggle_stream, width=12) self.connect_button.pack(side=tk.LEFT, padx=(0, 5)) # 截图按钮 self.snapshot_button = ttk.Button(control_frame, text="抓拍", command=self.take_snapshot, width=8, state=tk.DISABLED) self.snapshot_button.pack(side=tk.LEFT) # 员工信息标题 ttk.Label(right_frame, text="员工信息识别", style="Title.TLabel").pack(pady=(0, 10), anchor=tk.W, padx=10) # 员工信息卡片 info_card = ttk.Frame(right_frame, style="Card.TFrame") info_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 员工照片和基本信息 info_frame = ttk.Frame(info_card) info_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 左侧员工照片区域 avatar_frame = ttk.Frame(info_frame, width=180, height=200) avatar_frame.pack(side=tk.LEFT, padx=(0, 20), fill=tk.Y) self.avatar_label = ttk.Label(avatar_frame) self.avatar_label.pack(fill=tk.BOTH, expand=True) # 默认头像 self.show_default_avatar() # 右侧员工详细信息 detail_frame = ttk.Frame(info_frame) detail_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) ttk.Label(detail_frame, text="员工基本信息", style="Subtitle.TLabel").pack(anchor=tk.W, pady=(0, 10)) # 信息标签 - 使用Grid布局更精确控制 label_frame = ttk.Frame(detail_frame) label_frame.pack(fill=tk.X, pady=5) ttk.Label(label_frame, text="姓名:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=0, column=0, sticky="e", padx=5, pady=5) self.name_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.name_value.grid(row=0, column=1, sticky="w", padx=5, pady=5) ttk.Label(label_frame, text="工号:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=1, column=0, sticky="e", padx=5, pady=5) self.id_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.id_value.grid(row=1, column=1, sticky="w", padx=5, pady=5) ttk.Label(label_frame, text="部门:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=2, column=0, sticky="e", padx=5, pady=5) self.dept_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.dept_value.grid(row=2, column=1, sticky="w", padx=5, pady=5) ttk.Label(label_frame, text="职位:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=3, column=0, sticky="e", padx=5, pady=5) self.position_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.position_value.grid(row=3, column=1, sticky="w", padx=5, pady=5) ttk.Label(label_frame, text="打卡状态:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=4, column=0, sticky="e", padx=5, pady=5) self.status_value = ttk.Label(label_frame, text="未识别", width=20, anchor=tk.W, style="Info.TLabel") self.status_value.grid(row=4, column=1, sticky="w", padx=5, pady=5) # 打卡按钮 button_frame = ttk.Frame(detail_frame) button_frame.pack(fill=tk.X, pady=10) self.clock_button = ttk.Button(button_frame, text="打卡", command=self.clock_in, width=15, state=tk.DISABLED) self.clock_button.pack(side=tk.RIGHT, padx=(0, 10)) # 考勤记录标题 ttk.Label(right_frame, text="今日考勤记录", style="Title.TLabel").pack(pady=(10, 10), anchor=tk.W, padx=10) # 考勤记录表格 record_card = ttk.Frame(right_frame, style="Card.TFrame") record_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 创建表格 columns = ("time", "id", "name", "dept", "status") self.record_tree = ttk.Treeview(record_card, columns=columns, show="headings", height=8) # 设置列标题 self.record_tree.heading("time", text="时间", anchor=tk.W) self.record_tree.heading("id", text="工号", anchor=tk.W) self.record_tree.heading("name", text="姓名", anchor=tk.W) self.record_tree.heading("dept", text="部门", anchor=tk.W) self.record_tree.heading("status", text="状态", anchor=tk.W) # 设置列宽 self.record_tree.column("time", width=150, anchor=tk.W) self.record_tree.column("id", width=100, anchor=tk.W) self.record_tree.column("name", width=100, anchor=tk.W) self.record_tree.column("dept", width=120, anchor=tk.W) self.record_tree.column("status", width=80, anchor=tk.W) # 添加滚动条 scrollbar = ttk.Scrollbar(record_card, orient="vertical", command=self.record_tree.yview) self.record_tree.configure(yscrollcommand=scrollbar.set) # 布局 self.record_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 状态栏 self.status_var = tk.StringVar(value="系统就绪") status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 初始化变量 self.stream_active = False self.instance = vlc.Instance("--no-xlib") self.player = self.instance.media_player_new() # 模拟员工数据库 self.employees = { "1001": {"name": "张明", "dept": "技术部", "position": "高级工程师", "avatar": "avatar1.jpg"}, "1002": {"name": "李华", "dept": "市场部", "position": "市场经理", "avatar": "avatar2.jpg"}, "1003": {"name": "王芳", "dept": "财务部", "position": "会计", "avatar": "avatar3.jpg"}, "1004": {"name": "赵刚", "dept": "人力资源", "position": "招聘主管", "avatar": "avatar4.jpg"}, "1005": {"name": "陈晓", "dept": "产品部", "position": "产品经理", "avatar": "avatar5.jpg"}, } # 考勤记录 self.attendance_records = [] # 添加示例记录 self.add_sample_records() # 初始化线程队列 self.gui_queue = queue.Queue() self.root.after(100, self.process_queue) def process_queue(self): """处理队列中的GUI更新任务""" while not self.gui_queue.empty(): try: task = self.gui_queue.get_nowait() if task["type"] == "update_employee_info": self.name_value.config(text=task["name"]) self.id_value.config(text=task["id"]) self.dept_value.config(text=task["dept"]) self.position_value.config(text=task["position"]) self.status_value.config(text=task["status"]) self.show_employee_avatar(task["avatar"]) self.clock_button.config(state=tk.NORMAL) elif task["type"] == "update_status": self.status_var.set(task["message"]) elif task["type"] == "clock_in": self.clock_in_task(task["emp_id"], task["emp_name"], task["emp_dept"]) except queue.Empty: pass self.root.after(100, self.process_queue) def show_default_avatar(self): """显示默认头像""" default_img = Image.new('RGB', (180, 200), color='#3498db') draw = ImageDraw.Draw(default_img) try: font = ImageFont.truetype("arial.ttf", 24) except: font = ImageFont.load_default() draw.text((40, 85), "无数据", fill="white", font=font) default_photo = ImageTk.PhotoImage(default_img) self.avatar_label.config(image=default_photo) self.avatar_label.image = default_photo def add_sample_records(self): """添加示例考勤记录""" records = [ ("08:45:22", "1001", "张明", "技术部", "正常"), ("09:01:35", "1002", "李华", "市场部", "迟到"), ("09:05:47", "1003", "王芳", "财务部", "正常"), ("12:01:15", "1001", "张明", "技术部", "外出"), ("13:30:08", "1004", "赵刚", "人力资源", "正常"), ] for record in records: self.attendance_records.append(record) self.record_tree.insert("", tk.END, values=record) def toggle_stream(self): """切换视频流状态""" if self.stream_active: self.stop_stream() else: self.start_stream() def start_stream(self): """启动视频流""" url = self.url_entry.get().strip() if not url: messagebox.showerror("错误", "请输入有效的视频流URL") return try: media = self.instance.media_new(url) self.player.set_media(media) win_id = self.video_container.winfo_id() if os.name == 'nt': win_id = int(win_id) self.player.set_hwnd(win_id) else: self.player.set_xwindow(win_id) self.player.play() self.stream_active = True self.connect_button.config(text="停止监控") self.snapshot_button.config(state=tk.NORMAL) self.status_var.set(f"正在播放: {url}") # 启动视频流线程 threading.Thread(target=self.video_thread, daemon=True).start() # 启动识别线程 threading.Thread(target=self.recognition_thread, daemon=True).start() except Exception as e: messagebox.showerror("连接错误", f"无法连接到视频流: {str(e)}") self.status_var.set("连接失败") def video_thread(self): """视频流播放线程""" pass # 视频流由VLC内部处理,无需额外操作 def recognition_thread(self, out_dir: str = OUT_DIR): """使用实际工牌识别代码进行识别的线程""" cap = cv2.VideoCapture(self.url_entry.get().strip()) if not cap.isOpened(): print("[错误] 无法打开视频流") return while self.stream_active: ret, frame = cap.read() if not ret: print("[调试信息] 无法读取视频帧") continue # 保存当前帧为临时文件供main函数处理 temp_image_path = "temp_frame.jpg" OUT_DIR = "output" cv2.imwrite(OUT_DIR, frame) cv2.imwrite(temp_image_path, frame) print(f"[调试信息] 图像已保存至 {temp_image_path}") # 调用main.py 进行图像识别 try: print(f"[调试信息] 正在调用 main.py 处理 {temp_image_path}") subprocess.run(["python", "main.py", temp_image_path], check=True) print("[调试信息] main.py 执行完成") except subprocess.CalledProcessError as e: print(f"[错误信息] main.py 执行失败: {e}") # 读取main.py 输出的JSON文件 stem = os.path.splitext(os.path.basename(temp_image_path))[0] final_json = os.path.join(OUT_DIR, f"{stem}_face_result.json") if os.path.exists(final_json): print(f"[调试信息] JSON文件已找到: {final_json}") with open(final_json, "r", encoding="utf-8") as f: result = json.load(f) ocr_info = result.get("ocr", {}) emp_id = ocr_info.get("id") emp_name = ocr_info.get("name") emp_dept = ocr_info.get("department") # 打印识别结果 print(f"[调试信息] 识别结果: ID={emp_id}, 姓名={emp_name}, 部门={emp_dept}") # 将识别结果发送到GUI队列 task = { "type": "update_employee_info", "name": emp_name, "id": emp_id, "dept": emp_dept, "position": "未知", "avatar": os.path.join(OUT_DIR, f"{stem}_face.jpg") } self.gui_queue.put(task) time.sleep(1) # 控制识别频率 cap.release() def stop_stream(self): """停止视频流""" if self.player: self.player.stop() self.stream_active = False self.connect_button.config(text="启动监控") self.snapshot_button.config(state=tk.DISABLED) self.clock_button.config(state=tk.DISABLED) self.status_var.set("已停止视频流") # 清空员工信息 self.name_value.config(text="") self.id_value.config(text="") self.dept_value.config(text="") self.position_value.config(text="") self.status_value.config(text="未识别") self.show_default_avatar() def take_snapshot(self): """抓拍当前帧""" if self.stream_active: timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"snapshot_{timestamp}.png" self.player.video_take_snapshot(0, filename, 0, 0) messagebox.showinfo("抓拍成功", f"已保存截图: {filename}") self.status_var.set(f"截图已保存: {filename}") def show_employee_avatar(self, avatar_path): """显示员工头像""" try: colors = ["#3498db", "#2ecc71", "#e74c3c", "#f39c12", "#9b59b6"] color = random.choice(colors) img = Image.new('RGB', (180, 200), color=color) draw = ImageDraw.Draw(img) try: font = ImageFont.truetype("arial.ttf", 20) except: font = ImageFont.load_default() draw.text((40, 85), "员工照片", fill="white", font=font) photo = ImageTk.PhotoImage(img) self.avatar_label.config(image=photo) self.avatar_label.image = photo except Exception as e: print(f"头像加载错误: {e}") def clock_in(self): """员工打卡""" emp_id = self.id_value.cget("text") emp_name = self.name_value.cget("text") emp_dept = self.dept_value.cget("text") task = {"type": "clock_in", "emp_id": emp_id, "emp_name": emp_name, "emp_dept": emp_dept} self.gui_queue.put(task) def clock_in_task(self, emp_id, emp_name, emp_dept): """执行打卡逻辑""" current_time = time.strftime("%H:%M:%S") hour = int(time.strftime("%H")) minute = int(time.strftime("%M")) status = "迟到" if (hour > 9 or (hour == 9 and minute > 0)) else "正常" record = (current_time, emp_id, emp_name, emp_dept, status) self.attendance_records.append(record) self.record_tree.insert("", tk.END, values=record) self.status_value.config(text=f"已打卡 ({status})") self.status_var.set(f"{emp_name} 打卡成功! 时间: {current_time}") self.clock_button.config(state=tk.DISABLED) self.record_tree.see(self.record_tree.get_children()[-1]) if __name__ == "__main__": root = tk.Tk() app = EmployeeClockSystem(root) root.mainloop() 再这个代码基础上使用ttkboostrap主题包,使得界面更好看
08-23
import tkinter as tk import ttkbootstrap as tb import threading import socket import subprocess import time from ttkbootstrap.constants import * from PIL import Image, ImageTk, ImageDraw from tkinter import scrolledtext from datetime import datetime # 数据接收器类 class MLUDataReceiver: def __init__(self, callback): self.callback = callback self.devices = { "设备1": { "ip": "192.168.100.50", "status": "离线", "temperature": 0, "power": 0, "memory": 0, "bandwidth": 0, "mlu_avg_usage": 0, "cpu_avg_usage": 0 } } self.current_device = "设备1" self.running = True self.start_udp_receive() def parse_info(self, info_str): info = {} sections = info_str.split("---------------END---------------")[0].split("\n\n") for section in sections: lines = [line.strip() for line in section.splitlines() if line.strip()] if not lines: continue key = lines[0].split(":")[0].strip() if key == "温度信息": info.update(self.parse_temperature(lines[1:])) elif key == "功率信息": info.update(self.parse_power(lines[1:])) elif key == "内存信息": info.update(self.parse_memory(lines)) elif key == "带宽信息": info.update(self.parse_bandwidth(lines[1:])) elif key == "MLU信息": # 解析MLU核心利用率 mlu_info = self.parse_mlu_info(lines) info.update(mlu_info) elif key == "CPU信息": # 解析CPU核心利用率 cpu_info = self.parse_cpu_info(lines) info.update(cpu_info) return info def parse_temperature(self, lines): temp = 0 for line in lines: if "C" in line: try: temp = max(temp, float(line.split(":")[1].strip().replace(" C", ""))) except (IndexError, ValueError): continue return {"temperature": temp} def parse_power(self, lines): power = 0 for line in lines: if "W" in line: try: power = float(line.split(":")[1].strip().replace(" W", "")) except (IndexError, ValueError): continue return {"power": power} def parse_memory(self, lines): total = 0 used = 0 physical_memory_section = False for line in lines: if "Physical Memory Usage" in line: physical_memory_section = True elif physical_memory_section: if "Total" in line: total = self.parse_memory_value(line) elif "Used" in line: used = self.parse_memory_value(line) return {"memory": round(used / total * 100, 1) if total != 0 else 0} def parse_memory_value(self, line): try: value = line.split(":")[1].strip() num = float(value.split()[0]) unit = value.split()[1] if unit == "MiB": return num * 1024 * 1024 return num except (IndexError, ValueError): return 0 def parse_bandwidth(self, lines): bandwidth = 0 for line in lines: if "GB/s" in line: try: bandwidth = float(line.split(":")[1].strip().replace(" GB/s", "")) except (IndexError, ValueError): continue return {"bandwidth": bandwidth} def parse_mlu_info(self, lines): """解析MLU信息,包括平均利用率和各核心利用率""" mlu_avg_usage = 0 mlu_cores = [0.0] * 4 # 初始化4个核心的利用率 for line in lines: if "MLU Average" in line: try: parts = line.split(':') if len(parts) > 1: mlu_avg_usage = float(parts[1].strip().replace("%", "")) except (IndexError, ValueError): continue # 解析各核心利用率 for i in range(4): if f"MLU {i}" in line: try: parts = line.split(':') if len(parts) > 1: mlu_cores[i] = float(parts[1].strip().replace("%", "")) except (IndexError, ValueError): continue return { "mlu_avg_usage": mlu_avg_usage, "mlu_cores": mlu_cores } def parse_cpu_info(self, lines): """解析CPU信息,包括平均利用率和各核心利用率""" cpu_avg_usage = 0 cpu_cores = [0.0] * 4 # 初始化4个核心的利用率 for line in lines: if "Device CPU Chip" in line: try: parts = line.split(':') if len(parts) > 1: cpu_avg_usage = float(parts[1].strip().replace("%", "")) except (IndexError, ValueError): continue # 解析各核心利用率 for i in range(4): if f"Device CPU Core {i}" in line: try: parts = line.split(':') if len(parts) > 1: cpu_cores[i] = float(parts[1].strip().replace("%", "")) except (IndexError, ValueError): continue return { "cpu_avg_usage": cpu_avg_usage, "cpu_cores": cpu_cores } def start_udp_receive(self): def read_config(): config = {} try: with open('config.txt', 'r') as config_file: for line in config_file: if '=' in line: key, value = line.strip().split('=', 1) config[key] = value print("Read config success.") except FileNotFoundError: print("Unable to open config file!") return None return config config = read_config() if config is None: return server_ip = config.get('SERVER_IP') server_port = int(config.get('SERVER_PORT')) client_ip = config.get('CLIENT_IP') client_port = int(config.get('CLIENT_PORT')) print("ServerIP: {}".format(server_ip)) print("ServerPort: {}".format(server_port)) print("ClientIP: {}".format(client_ip)) print("ClientPort: {}".format(client_port)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) print("Set socket ok.") try: sock.bind((client_ip, client_port)) print("Bind success.") except OSError: print("Bind error!") return def receive_data(): print("Start receive data.") try: while self.running: data, addr = sock.recvfrom(4096) try: info_str = data.decode('utf-8') device = self.devices[self.current_device] # ping检测逻辑 ip = device["ip"] try: result = subprocess.run(['ping', '-c', '1', '-W', '1', ip], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode == 0: device["status"] = "在线" else: device["status"] = "离线" except Exception as e: print(f"Ping 检测出错: {e}") device["status"] = "离线" # 解析数据并更新设备信息 info = self.parse_info(info_str) device.update(info) # 通过回调传递数据 self.callback({ 'cpu_temp': device['temperature'], 'power': device['power'], 'mem_usage': device['memory'], 'bandwidth': device['bandwidth'] * 8000, # 转换为Mbps 'mlu_usage': device['mlu_avg_usage'], 'mlu_cores': device.get('mlu_cores', [0, 0, 0, 0]), 'cpu_usage': device['cpu_avg_usage'], 'cpu_cores': device.get('cpu_cores', [0, 0, 0, 0]), 'status': device['status'] }) except UnicodeDecodeError: print("解码数据时出错,请检查数据编码。") except OSError: print("Receive data error!") except Exception as e: print(f"Unexpected error: {e}") finally: sock.close() receive_thread = threading.Thread(target=receive_data) receive_thread.daemon = True receive_thread.start() def stop(self): self.running = False # 增强型圆形进度条类 class EnhancedCircularProgressBar: def __init__(self, parent, size=200, thickness=20, font_size=16, title="", unit="", max_value=100): self.parent = parent self.size = size self.thickness = thickness self.font_size = font_size self.title = title self.unit = unit self.max_value = max_value self.current_value = 0 self.target_value = 0 self.animation_speed = 1.0 # 动画速度因子 # 获取当前主题颜色 style = tb.Style() self.text_color = style.colors.fg self.bg_color = style.colors.bg self.secondary_bg = style.colors.inputbg # 背景圆环使用输入框背景色 # 创建画布 self.canvas = tk.Canvas( parent, width=size, height=size, highlightthickness=0, bd=0, bg=self.bg_color # 使用主题背景色 ) # 计算圆心和半径 self.center_x = size / 2 self.center_y = size / 2 self.radius = (size - thickness) / 2 - 5 # 绘制背景圆环 self.draw_background() # 绘制文本 self.text_id = self.canvas.create_text( self.center_x, self.center_y, text="0%", fill=self.text_color, # 使用主题前景色 font=("Arial", font_size, "bold") ) # 绘制标题 self.title_id = self.canvas.create_text( self.center_x, self.center_y + 40, text=title, fill=self.text_color, # 使用主题前景色 font=("Arial", int(font_size*0.8)) ) # 初始绘制 self.set_value(0) def draw_background(self): """绘制背景圆环""" # 计算圆环的外接矩形坐标 x0 = self.center_x - self.radius y0 = self.center_y - self.radius x1 = self.center_x + self.radius y1 = self.center_y + self.radius # 绘制背景圆环 self.bg_arc = self.canvas.create_arc( x0, y0, x1, y1, start=0, # 起始角度 extent=360, # 360度完整圆环 width=self.thickness, outline=self.secondary_bg, # 使用主题输入框背景色 style=tk.ARC ) def interpolate_color(self, progress): """根据进度值插值计算颜色(绿-黄-橙-红过渡)""" # 定义颜色过渡点 color_points = [ (0.00, (0, 180, 0)), # 深绿色 (0.25, (170, 255, 0)), # 黄绿色 (0.50, (255, 255, 0)), # 黄色 (0.75, (255, 170, 0)), # 橙色 (1.00, (255, 0, 0)) # 红色 ] # 找到当前进度所在的区间 for i in range(1, len(color_points)): if progress <= color_points[i][0]: # 计算区间内的比例 t = (progress - color_points[i-1][0]) / (color_points[i][0] - color_points[i-1][0]) # 线性插值计算RGB值(不使用numpy) r1, g1, b1 = color_points[i-1][1] r2, g2, b2 = color_points[i][1] r = int(r1 + t * (r2 - r1)) g = int(g1 + t * (g2 - g1)) b = int(b1 + t * (b2 - b1)) return f"#{r:02x}{g:02x}{b:02x}" # 默认返回红色 return "#ff0000" def set_value(self, value, animate=True): """设置当前值并更新进度条""" # 确保值在合理范围内 value = max(0, min(value, self.max_value)) if animate: self.target_value = value # 启动动画(如果尚未运行) if abs(self.current_value - self.target_value) > 0.1: self.animate_step() else: self.target_value = value self.current_value = value self.update_display() def update_display(self): """更新进度条显示""" # 计算进度比例 progress = self.current_value / self.max_value # 计算角度(从-90度开始,即12点钟方向) angle = min(progress * 360, 359.99) # 清除旧的前景圆环 if hasattr(self, 'fg_arc'): self.canvas.delete(self.fg_arc) # 计算圆环的外接矩形坐标 x0 = self.center_x - self.radius y0 = self.center_y - self.radius x1 = self.center_x + self.radius y1 = self.center_y + self.radius # 获取插值颜色 color = self.interpolate_color(progress) # 绘制前景圆环(进度指示) self.fg_arc = self.canvas.create_arc( x0, y0, x1, y1, start=90, # 从12点钟方向开始 extent=angle, # 根据值计算角度 width=self.thickness, outline=color, style=tk.ARC ) # 提升前景圆环的显示层级 self.canvas.tag_raise(self.fg_arc) # 更新文本显示 - 修改带宽显示 if "带宽" in self.title: # 对于带宽,显示实际值加单位 display_text = f"{self.current_value:.1f} {self.unit}" elif self.unit == "%": # 对于百分比,显示百分比值 display_text = f"{progress*100:.1f}%" else: # 其他情况显示数值加单位 display_text = f"{self.current_value:.1f} {self.unit}" self.canvas.itemconfig(self.text_id, text=display_text) def animate_step(self): """执行一步动画(不使用numpy)""" if abs(self.current_value - self.target_value) < 0.1: self.current_value = self.target_value else: # 使用缓动函数实现平滑动画 diff = self.target_value - self.current_value self.current_value += diff * 0.2 * self.animation_speed self.update_display() # 如果未达到目标值,继续动画 if abs(self.current_value - self.target_value) > 0.1: self.parent.after(16, self.animate_step) # 约60fps # 详细窗口类 class DetailWindow: def __init__(self, parent, title, data, core_data, core_type): self.parent = parent self.window = tb.Toplevel(parent) self.window.title(title) self.window.geometry("650x450") self.window.resizable(True, True) # 设置主题与主窗口一致 style = tb.Style() current_theme = self.parent.style.theme_use() style.theme_use(current_theme) # 主框架 main_frame = tb.Frame(self.window, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 标题 tb.Label( main_frame, text=title, font=("Arial", 16, "bold"), bootstyle=PRIMARY ).pack(pady=(0, 15)) # 平均利用率 avg_frame = tb.Frame(main_frame) avg_frame.pack(fill=tk.X, pady=5) # 保存平均利用率标签引用以便更新 self.avg_label = tb.Label( avg_frame, text=f"平均利用率: {data:.1f}%", font=("Arial", 14), bootstyle=INFO ) self.avg_label.pack(side=tk.LEFT) # 核心利用率容器 cores_frame = tb.Frame(main_frame) cores_frame.pack(fill=tk.BOTH, expand=True, pady=10) # 创建4列 for i in range(4): cores_frame.columnconfigure(i, weight=1) # 创建核心利用率进度条 self.core_bars = [] self.core_labels = [] # 保存核心利用率标签引用 for i, usage in enumerate(core_data): frame = tb.Frame(cores_frame) frame.grid(row=0, column=i, padx=10, pady=10, sticky="nsew") # 核心标题 tb.Label( frame, text=f"{core_type} 核心 {i}", font=("Arial", 12), bootstyle=SECONDARY ).pack(pady=(0, 5)) # 进度条 progress_bar = EnhancedCircularProgressBar( frame, size=120, thickness=12, title="", unit="%", max_value=100 ) progress_bar.set_value(usage) progress_bar.canvas.pack() self.core_bars.append(progress_bar) # 利用率标签 core_label = tb.Label( frame, text=f"{usage:.1f}%", font=("Arial", 12), bootstyle=INFO ) core_label.pack(pady=(5, 0)) self.core_labels.append(core_label) # 保存标签引用 def update_values(self, avg_usage, core_usages): """更新详细窗口中的所有值""" # 更新平均利用率标签 self.avg_label.config(text=f"平均利用率: {avg_usage:.1f}%") # 更新核心利用率进度条和标签 for i, (bar, label) in enumerate(zip(self.core_bars, self.core_labels)): if i < len(core_usages): bar.set_value(core_usages[i]) label.config(text=f"{core_usages[i]:.1f}%") # 队列监控组件类 class QueueMonitor: def __init__(self, parent, server_ip="192.168.100.50", monitor_port=9997): self.parent = parent # 创建容器框架并设置固定尺寸 self.container = tb.Frame(parent) self.container.pack(fill=tk.BOTH, expand=True) self.container.config(width=900, height=600) self.container.pack_propagate(False) # 阻止容器根据内容调整大小 # 创建内部框架,所有内容放在这个内部框架中 self.frame = tb.Frame(self.container) self.frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 连接状态变量 self.connected = False self.server_ip = server_ip self.monitor_port = monitor_port self.monitor_running = True # 任务队列数据 self.cpu_queue = [] self.mlu_queue = [] self.current_cpu_task = "" self.current_mlu_task = "" self.cpu_eta = "" self.mlu_eta = "" self.report_time = "" # 新增:存储报告时间 # 创建UI self.create_widgets() # 启动状态监听线程 self.start_monitor_thread() def create_widgets(self): """创建专业UI界面,优化布局""" # 主容器 main_container = tb.Frame(self.frame, padding=(15, 15)) main_container.pack(fill=tk.BOTH, expand=True) # 标题区域 header_frame = tb.Frame(main_container, padding=(10, 10)) header_frame.pack(fill=tk.X, pady=(0, 10)) tb.Label( header_frame, text="任务队列监控", font=("Segoe UI", 16, "bold"), ).pack(side=tk.LEFT, padx=6) right_status_frame = tb.Frame(header_frame) right_status_frame.pack(side=tk.RIGHT) # 报告时间标签 self.time_label = tb.Label( right_status_frame, text="报告时间: -", bootstyle=INFO, font=("Segoe UI", 10), ) self.time_label.pack(side=tk.TOP, pady=(0, 5)) # 上方元素,底部留空 # 分隔线 separator = tb.Separator( right_status_frame, orient=tk.HORIZONTAL, bootstyle=SECONDARY ) separator.pack(fill=tk.X, pady=3) # 在时间和状态之间添加分隔线 # 连接状态指示器 self.status_indicator = tb.Label( right_status_frame, text="● 未连接", bootstyle=DANGER, font=("Segoe UI", 10, "bold"), ) self.status_indicator.pack(side=tk.TOP) # 下方元素 # 控制面板 control_frame = tb.Frame(main_container) control_frame.pack(fill=tk.X, pady=(0, 10)) # 服务器配置网格 config_grid = tb.Frame(control_frame) config_grid.pack(fill=tk.X, padx=5, pady=5) # 服务器地址 tb.Label( config_grid, text="服务器地址:", font=("Segoe UI", 9), ).grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) self.ip_entry = tb.Entry( config_grid, width=22, font=("Consolas", 9), ) self.ip_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W) self.ip_entry.insert(0, self.server_ip) # 监控端口 tb.Label( config_grid, text="监控端口:", font=("Segoe UI", 9), ).grid(row=0, column=2, padx=(15, 5), pady=5, sticky=tk.W) self.monitor_entry = tb.Entry( config_grid, width=8, font=("Consolas", 9), ) self.monitor_entry.grid(row=0, column=3, padx=5, pady=5, sticky=tk.W) self.monitor_entry.insert(0, str(self.monitor_port)) # 连接按钮 self.connect_btn = tb.Button( config_grid, text="连接监控", command=self.connect_to_server, bootstyle=(OUTLINE,DARK), width=10 ) self.connect_btn.grid(row=0, column=4, padx=(15, 5)) # 队列状态区域 queue_frame = tb.Frame(main_container) queue_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) # 创建网格布局 - 改为2列 queue_frame.columnconfigure(0, weight=1) queue_frame.columnconfigure(1, weight=1) queue_frame.rowconfigure(0, weight=1) # CPU队列面板 - 常驻队列和调度队列两个子面板 cpu_frame = tb.LabelFrame( queue_frame, text="CPU队列状态", padding=10, ) cpu_frame.grid(row=0, column=0, padx=(0, 5), sticky="nsew") # CPU队列内容 - 使用网格布局分为常驻队列和调度队列 cpu_grid = tb.Frame(cpu_frame) cpu_grid.pack(fill=tk.BOTH, expand=True) # 常驻队列部分 cpu_resident_frame = tb.LabelFrame( cpu_grid, text="常驻队列", padding=5, bootstyle="info" ) cpu_resident_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") # 常驻队列标题 tb.Label( cpu_resident_frame, text="当前任务:", font=("Segoe UI", 9, "bold"), ).pack(anchor=tk.W, padx=5, pady=(0, 5)) # 常驻队列内容 self.cpu_resident_text = scrolledtext.ScrolledText( cpu_resident_frame, wrap=tk.WORD, height=4, font=("Consolas", 9), state=tk.DISABLED ) self.cpu_resident_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=(0, 5)) # 调度队列部分 cpu_schedule_frame = tb.LabelFrame( cpu_grid, text="调度队列", padding=5, bootstyle="warning" ) cpu_schedule_frame.grid(row=1, column=0, padx=5, pady=5, sticky="nsew") # 调度队列标题 tb.Label( cpu_schedule_frame, text="当前任务:", font=("Segoe UI", 9, "bold"), ).pack(anchor=tk.W, padx=5, pady=(0, 5)) # 调度队列内容 self.cpu_schedule_text = scrolledtext.ScrolledText( cpu_schedule_frame, wrap=tk.WORD, height=4, font=("Consolas", 9), state=tk.DISABLED ) self.cpu_schedule_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=(0, 5)) # 设置网格权重 cpu_grid.rowconfigure(0, weight=1) cpu_grid.rowconfigure(1, weight=1) cpu_grid.columnconfigure(0, weight=1) # MLU队列面板 - 同样改为常驻队列和调度队列两个子面板 mlu_frame = tb.LabelFrame( queue_frame, text="MLU队列状态", padding=10, ) mlu_frame.grid(row=0, column=1, padx=(5, 0), sticky="nsew") # MLU队列内容 - 使用网格布局分为常驻队列和调度队列 mlu_grid = tb.Frame(mlu_frame) mlu_grid.pack(fill=tk.BOTH, expand=True) # 常驻队列部分 mlu_resident_frame = tb.LabelFrame( mlu_grid, text="常驻队列", padding=5, bootstyle="info" ) mlu_resident_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") # 常驻队列标题 tb.Label( mlu_resident_frame, text="当前任务:", font=("Segoe UI", 9, "bold"), ).pack(anchor=tk.W, padx=5, pady=(0, 5)) # 常驻队列内容 self.mlu_resident_text = scrolledtext.ScrolledText( mlu_resident_frame, wrap=tk.WORD, height=4, font=("Consolas", 9), state=tk.DISABLED ) self.mlu_resident_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=(0, 5)) # 调度队列部分 mlu_schedule_frame = tb.LabelFrame( mlu_grid, text="调度队列", padding=5, bootstyle="warning" ) mlu_schedule_frame.grid(row=1, column=0, padx=5, pady=5, sticky="nsew") # 调度队列标题 tb.Label( mlu_schedule_frame, text="当前任务:", font=("Segoe UI", 9, "bold"), ).pack(anchor=tk.W, padx=5, pady=(0, 5)) # 调度队列内容 self.mlu_schedule_text = scrolledtext.ScrolledText( mlu_schedule_frame, wrap=tk.WORD, height=4, font=("Consolas", 9), state=tk.DISABLED ) self.mlu_schedule_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=(0, 5)) # 设置网格权重 mlu_grid.rowconfigure(0, weight=1) mlu_grid.rowconfigure(1, weight=1) mlu_grid.columnconfigure(0, weight=1) # 底部状态栏 status_frame = tb.Frame(self.frame) status_frame.pack(fill=tk.X, side=tk.BOTTOM) self.status_var = tk.StringVar() self.status_var.set("就绪") status_label = tb.Label( status_frame, textvariable=self.status_var, font=("Segoe UI", 9), ) status_label.pack(side=tk.LEFT, padx=15) # 任务计数 self.task_count_var = tk.StringVar() self.task_count_var.set("总任务: 0") task_count_label = tb.Label( status_frame, textvariable=self.task_count_var, font=("Segoe UI", 9), ) task_count_label.pack(side=tk.RIGHT, padx=15) def connect_to_server(self): """连接到服务器监控""" try: self.server_ip = self.ip_entry.get() self.monitor_port = int(self.monitor_entry.get()) # 重启监控线程以使用新的配置 self.monitor_running = False time.sleep(0.5) # 等待线程结束 self.start_monitor_thread() self.connected = True self.status_indicator.config(text="● 已连接", bootstyle=SUCCESS) self.status_var.set(f"已连接到 {self.server_ip}:{self.monitor_port}") self.update_status("监控连接成功") except Exception as e: self.update_status(f"连接失败: {str(e)}") self.status_indicator.config(text="● 连接失败", bootstyle=DANGER) def start_monitor_thread(self): """启动监控线程""" self.monitor_running = True threading.Thread(target=self.monitor_status, daemon=True).start() self.update_status("监控线程已启动") def monitor_status(self): """监听服务器状态""" monitor_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: monitor_socket.bind(('0.0.0.0', self.monitor_port)) self.update_status(f"开始监听端口: {self.monitor_port}") self.update_status(f"等待来自 {self.server_ip} 的数据...") except Exception as e: self.update_status(f"端口绑定失败: {str(e)}") return while self.monitor_running: try: data, addr = monitor_socket.recvfrom(4096) if addr[0] != self.server_ip: self.update_status(f"收到来自 {addr[0]} 的数据,已忽略") continue try: status_str = data.decode('utf-8') self.parse_queue_status(status_str) self.update_status("收到队列状态更新") self.last_update_var.set(f"更新: {datetime.now().strftime('%H:%M:%S')}") except UnicodeDecodeError: self.update_status("收到非文本数据,无法解码") except Exception as e: self.update_status(f"监听错误: {str(e)}") time.sleep(1) monitor_socket.close() self.update_status("监控线程已停止") def parse_queue_status(self, status_str): """解析队列状态字符串 - 更新解析逻辑以适应新格式""" # 初始化队列数据 self.cpu_resident_queue = [] # CPU常驻队列 self.cpu_schedule_queue = [] # CPU调度队列 self.mlu_resident_queue = [] # MLU常驻队列 self.mlu_schedule_queue = [] # MLU调度队列 lines = status_str.split('\n') if len(lines) < 8: self.update_status("状态信息格式错误,无法解析") return # 解析队列状态报告时间 self.report_time = "未知时间" for line in lines: if "队列状态报告" in line: # 尝试提取时间部分 # 行格式:队列状态报告 - 2023-03-02 14:30:46 parts = line.split(' - ') if len(parts) > 1: self.report_time = parts[1].strip() break # 更新报告时间标签 self.time_label.config(text=f"报告时间: {self.report_time}") # 解析CPU队列 current_section = None for line in lines: line = line.strip() # 检测队列类型 if "CPU常驻队列" in line: current_section = "CPU_RESIDENT" continue elif "CPU调度队列" in line: current_section = "CPU_SCHEDULE" continue elif "MLU常驻队列" in line: current_section = "MLU_RESIDENT" continue elif "MLU调度队列" in line: current_section = "MLU_SCHEDULE" continue elif "无任务" in line: # 空队列标记 current_section = None continue # 解析任务行 if current_section and line.startswith("任务"): # 提取任务信息 task_info = line.split(":", 1)[1].strip() # 添加到相应队列 if current_section == "CPU_RESIDENT": self.cpu_resident_queue.append(task_info) elif current_section == "CPU_SCHEDULE": self.cpu_schedule_queue.append(task_info) elif current_section == "MLU_RESIDENT": self.mlu_resident_queue.append(task_info) elif current_section == "MLU_SCHEDULE": self.mlu_schedule_queue.append(task_info) # 更新UI self.update_queue_display() def update_queue_display(self): """更新队列显示 - 适配新的队列结构""" # CPU常驻队列 self.cpu_resident_text.config(state=tk.NORMAL) self.cpu_resident_text.delete(1.0, tk.END) if self.cpu_resident_queue: for task in self.cpu_resident_queue: self.cpu_resident_text.insert(tk.END, f"• {task}\n") else: self.cpu_resident_text.insert(tk.END, "无任务") self.cpu_resident_text.config(state=tk.DISABLED) # CPU调度队列 self.cpu_schedule_text.config(state=tk.NORMAL) self.cpu_schedule_text.delete(1.0, tk.END) if self.cpu_schedule_queue: for task in self.cpu_schedule_queue: self.cpu_schedule_text.insert(tk.END, f"• {task}\n") else: self.cpu_schedule_text.insert(tk.END, "无任务") self.cpu_schedule_text.config(state=tk.DISABLED) # MLU常驻队列 self.mlu_resident_text.config(state=tk.NORMAL) self.mlu_resident_text.delete(1.0, tk.END) if self.mlu_resident_queue: for task in self.mlu_resident_queue: self.mlu_resident_text.insert(tk.END, f"• {task}\n") else: self.mlu_resident_text.insert(tk.END, "无任务") self.mlu_resident_text.config(state=tk.DISABLED) # MLU调度队列 self.mlu_schedule_text.config(state=tk.NORMAL) self.mlu_schedule_text.delete(1.0, tk.END) if self.mlu_schedule_queue: for task in self.mlu_schedule_queue: self.mlu_schedule_text.insert(tk.END, f"• {task}\n") else: self.mlu_schedule_text.insert(tk.END, "无任务") self.mlu_schedule_text.config(state=tk.DISABLED) # 更新任务计数 total_tasks = (len(self.cpu_resident_queue) + len(self.cpu_schedule_queue) + len(self.mlu_resident_queue) + len(self.mlu_schedule_queue)) self.task_count_var.set(f"总任务: {total_tasks}") def update_status(self, message): """更新状态日志""" # 更新状态栏 self.status_var.set(message) def stop(self): """停止监控""" self.monitor_running = False time.sleep(0.2) # 等待线程结束 # 主应用类 class SystemMonitorApp: def __init__(self): # 创建主窗口 - 使用darkly主题 self.root = tb.Window(themename="darkly", title="系统资源监控", size=(1650, 900)) self.root.minsize(1300, 800) self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.style.configure( 'Custom.TFrame', background=self.style.colors.inputbg # 使用输入框背景色作为次级背景 ) # 设置样式 self.style = tb.Style() # 创建主框架 - 使用网格布局 main_frame = tb.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 配置网格权重 main_frame.columnconfigure(0, weight=1) # 左侧面板 main_frame.columnconfigure(1, weight=3) # 中间面板 main_frame.columnconfigure(2, weight=1) # 右侧面板 main_frame.rowconfigure(1, weight=1) # 获取当前主题颜色 self.bg_color = self.style.colors.bg self.secondary_bg = self.style.colors.selectbg # 使用更深一级的背景色 # 创建自定义样式 self.style.configure( 'Custom.TFrame', background=self.style.colors.inputbg # 使用输入框背景色作为次级背景 ) self.style.configure( 'Custom.TButton', background=self.style.colors.inputbg, # 使用次级背景 bordercolor=self.style.colors.border, # 边框颜色 darkcolor=self.style.colors.inputbg, # 深色状态颜色 lightcolor=self.style.colors.inputbg # 浅色状态颜色 ) self.style.configure( 'Custom.TLabel', background=self.style.colors.inputbg, # 使用次级背景 foreground=self.style.colors.fg # 前景色(文本颜色)使用主题的前景色 ) # ========== 顶部控制栏 ========== control_bar = tb.Frame(main_frame, padding=(10, 5)) control_bar.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 10)) # 采样频率滑块 tb.Label(control_bar, text="采样频率:", bootstyle=PRIMARY).pack(side=tk.LEFT, padx=(20, 5)) self.sampling_rate = tk.IntVar(value=1) tb.Scale( control_bar, from_=0.5, to=5, length=120, orient=tk.HORIZONTAL, variable=self.sampling_rate, bootstyle=PRIMARY ).pack(side=tk.LEFT, padx=5) # 动画速度滑块 tb.Label(control_bar, text="动画速度:", bootstyle=PRIMARY).pack(side=tk.LEFT, padx=(20, 5)) self.animation_speed = tk.DoubleVar(value=1.0) tb.Scale( control_bar, from_=0.5, to=3.0, length=120, orient=tk.HORIZONTAL, variable=self.animation_speed, bootstyle=PRIMARY ).pack(side=tk.LEFT, padx=5) # 控制按钮 tb.Button( control_bar, text="启动", bootstyle=(OUTLINE, PRIMARY), width=8, command=self.start_monitoring ).pack(side=tk.LEFT, padx=5) tb.Button( control_bar, text="暂停", bootstyle=(OUTLINE, PRIMARY), width=8, command=self.stop_monitoring ).pack(side=tk.LEFT, padx=5) tb.Button( control_bar, text="重置", bootstyle=(OUTLINE, PRIMARY), width=8, command=self.reset_all ).pack(side=tk.LEFT, padx=5) # 状态指示器 self.status_indicator = tb.Label( control_bar, text="● 等待连接", bootstyle=(WARNING, INVERSE), font=("Arial", 10), padding=(10, 0) ) self.status_indicator.pack(side=tk.RIGHT, padx=(10, 0)) # ========== 左侧设置面板 - 使用更深背景色 ========== settings_frame = tb.Frame( main_frame, padding=10, style='Custom.TFrame' ) settings_frame.grid(row=1, column=0, sticky="nsew", padx=(0, 10), pady=(0, 10)) # 添加标题 tb.Label( settings_frame, text="监控设置", bootstyle=PRIMARY, font=("Arial", 10, "bold"), style='Custom.TLabel' # 应用自定义标签样式 ).pack(anchor=tk.W, pady=(0, 10)) # 信息类型选择 - 四个垂直排列的按钮 tb.Label( settings_frame, text="信息类型:", bootstyle=PRIMARY ).pack(anchor=tk.W, pady=(0, 5)) # 创建按钮容器 - 使用垂直排列 button_container = tb.Frame(settings_frame, style='Custom.TFrame') button_container.pack(fill=tk.X, pady=(0, 10)) # 四个独立按钮 - 垂直排列 self.info_buttons = {} info_types = ["总体信息", "MLU信息", "CPU信息", "队列信息"] button_styles = [PRIMARY, PRIMARY, PRIMARY, PRIMARY] for i, (info_type, style) in enumerate(zip(info_types, button_styles)): btn = tb.Button( button_container, text=info_type, bootstyle=(OUTLINE,DARK), width=15, command=lambda t=info_type: self.set_info_type(t) ) # 存储按钮的样式信息 btn.pack(side=tk.TOP, pady=3, fill=tk.X) self.info_buttons[info_type] = btn # 默认选中总体信息 self.info_type_var = tk.StringVar(value="总体信息") self.update_button_style("总体信息") # 分隔线 tb.Separator(settings_frame, bootstyle=SECONDARY).pack(fill=tk.X, pady=10) # 操作按钮框架 - 垂直排列 button_frame = tb.Frame(settings_frame) button_frame.pack(fill=tk.X, pady=(10, 5)) # ========== 中间监控面板 ========== self.monitor_frame = tb.Frame(main_frame, bootstyle="default") self.monitor_frame.grid(row=1, column=1, sticky="nsew", pady=(0, 10)) # 创建卡片容器 - 固定尺寸 self.card_container = tb.Frame(self.monitor_frame) self.card_container.pack(fill=tk.BOTH, expand=True) self.card_container.config(width=1100, height=700) self.card_container.pack_propagate(False) # 创建资源监控卡片 self.resource_card = tb.Frame(self.card_container) self.resource_card.pack(fill=tk.BOTH, expand=True) # 标题 tb.Label( self.resource_card, text="系统资源实时监控", font=("Arial", 16, "bold"), bootstyle=PRIMARY ).pack(pady=(0, 15)) # 创建进度条容器框架 progress_container = tb.Frame(self.resource_card) progress_container.pack(fill=tk.BOTH, expand=True, pady=5) # 创建监控指标配置 monitor_config = [ {"title": "CPU温度", "unit": "°C", "max_value": 100, "thickness": 18}, {"title": "功耗", "unit": "W", "max_value": 15, "thickness": 18}, {"title": "内存使用", "unit": "%", "max_value": 100, "thickness": 18}, {"title": "网络带宽", "unit": "Mbps", "max_value": 1000, "thickness": 18}, {"title": "MLU利用率", "unit": "%", "max_value": 100, "thickness": 18}, {"title": "CPU利用率", "unit": "%", "max_value": 100, "thickness": 18} ] # 使用网格布局排列进度条 self.progress_bars = [] for i, config in enumerate(monitor_config): frame = tb.Frame(progress_container) frame.grid(row=i//3, column=i%3, padx=15, pady=15, sticky="nsew") # 创建增强型进度条 progress_bar = EnhancedCircularProgressBar( frame, size=220, thickness=config["thickness"], title=config["title"], unit=config["unit"], max_value=config["max_value"] ) self.progress_bars.append(progress_bar) progress_bar.canvas.pack(fill=tk.BOTH, expand=True) # 设置网格列权重 for i in range(3): progress_container.columnconfigure(i, weight=1) for i in range(2): progress_container.rowconfigure(i, weight=1) # 创建队列监控卡片 self.queue_card = QueueMonitor(self.card_container) self.queue_card.container.pack_forget() # 初始隐藏 # 默认显示资源监控卡片 self.resource_card.pack(fill=tk.BOTH, expand=True) # ========== 右侧信息面板 - 使用更深背景色 ========== info_frame = tb.Frame( main_frame, padding=10, style='Custom.TFrame' ) info_frame.grid(row=1, column=2, sticky="nsew", padx=(10, 0), pady=(0, 10)) # 添加标题 tb.Label( info_frame, text="系统信息", bootstyle=PRIMARY, font=("Arial", 10, "bold"), style='Custom.TLabel' # 应用自定义标签样式 ).pack(anchor=tk.W, pady=(0, 10)) # 系统信息标签 - 调整标签宽度 info_labels = [ ("设备型号:", "MLU220"), ("操作系统:", "Ubuntu 20.04 LTS"), ("处理器:", "4核ARM Cortex-A53 CPU"), ("内存总量:", "4GB LPDDR4x"), ("MLU数量:", "4"), ("网络接口:", "PCIe 3.0×4接口") ] for label, value in info_labels: frame = tb.Frame(info_frame, style='Custom.TFrame') frame.pack(fill=tk.X, pady=3) # 增加标签宽度,确保显示完整 tb.Label(frame, text=label, width=12, anchor=tk.W, bootstyle=PRIMARY).pack(side=tk.LEFT) tb.Label(frame, text=value, bootstyle=INFO, anchor=tk.W).pack(side=tk.LEFT, fill=tk.X, expand=True) # 分隔线 tb.Separator(info_frame, bootstyle=SECONDARY).pack(fill=tk.X, pady=10) # 实时状态 tb.Label(info_frame, text="实时状态", bootstyle=PRIMARY).pack(anchor=tk.W, pady=(0, 5)) self.realtime_labels = {} status_items = [ ("CPU温度", "cpu_temp", "°C"), ("功耗", "power", "W"), ("内存使用", "memory", "%"), ("网络带宽", "bandwidth", "Mbps"), ("MLU利用率", "mlu_usage", "%"), ("CPU利用率", "cpu_usage", "%") ] for name, key, unit in status_items: frame = tb.Frame(info_frame, style='Custom.TFrame') frame.pack(fill=tk.X, pady=2) # 增加标签宽度 tb.Label(frame, text=name, width=14, anchor=tk.W, bootstyle=PRIMARY).pack(side=tk.LEFT) value_label = tb.Label(frame, text="0.0", width=10, anchor=tk.W, bootstyle=INFO) value_label.pack(side=tk.LEFT) tb.Label(frame, text=unit, bootstyle=PRIMARY).pack(side=tk.LEFT) self.realtime_labels[key] = value_label # 核心利用率框架 self.core_usage_frame = tb.LabelFrame( info_frame, text="核心利用率", bootstyle="info", padding=5 ) self.core_usage_frame.pack(fill=tk.X, pady=10) # 创建标签用于显示核心利用率 - 调整布局 self.core_labels = {} for i in range(4): # 4个核心 frame = tb.Frame(self.core_usage_frame) frame.pack(fill=tk.X, pady=2) # 核心标题 - 增加宽度 tb.Label(frame, text=f"核心 {i}:", width=10, anchor=tk.W, bootstyle=PRIMARY).pack(side=tk.LEFT) # CPU核心标签 cpu_frame = tb.Frame(frame) cpu_frame.pack(side=tk.LEFT, padx=(0, 5)) tb.Label(cpu_frame, text="CPU:", anchor=tk.W, bootstyle=PRIMARY).pack(side=tk.LEFT) cpu_label = tb.Label(cpu_frame, text="0.0%", width=6, anchor=tk.W, bootstyle=INFO) cpu_label.pack(side=tk.LEFT) self.core_labels[f"cpu_core_{i}"] = cpu_label # MLU核心标签 mlu_frame = tb.Frame(frame) mlu_frame.pack(side=tk.LEFT) tb.Label(mlu_frame, text="MLU:", anchor=tk.W, bootstyle=PRIMARY).pack(side=tk.LEFT) mlu_label = tb.Label(mlu_frame, text="0.0%", width=6, anchor=tk.W, bootstyle=INFO) mlu_label.pack(side=tk.LEFT) self.core_labels[f"mlu_core_{i}"] = mlu_label # ========== 状态栏 ========== self.status = tb.Label( self.root, text="系统准备就绪 | 当前设备: MLU220 | 信息类型: 总体信息", bootstyle=(SECONDARY, INVERSE), anchor=tk.CENTER ) self.status.pack(side=tk.BOTTOM, fill=tk.X) # 监控控制变量 self.monitoring_active = False self.monitoring_thread = None # 创建数据接收器 self.data_receiver = MLUDataReceiver(self.data_received) # 存储详细窗口引用 self.detail_window = None self.last_data = None # 初始状态 self.status_indicator.config(text="● 等待数据", bootstyle=(WARNING, INVERSE)) # 自动启动监控和连接队列监控 self.root.after(1000, self.auto_start) self.root.mainloop() def auto_start(self): """自动启动监控和连接队列监控""" # 自动启动监控 self.start_monitoring() # 自动连接队列监控 if hasattr(self, 'queue_card'): self.queue_card.connect_to_server() def update_button_style(self, active_type): """更新按钮样式以显示当前选中的信息类型""" for info_type, button in self.info_buttons.items(): if info_type == active_type: # 当前选中的按钮使用实心样式 button.configure(bootstyle=PRIMARY) else: # 其他按钮使用轮廓样式+自定义背景 button.configure( bootstyle=(OUTLINE, PRIMARY), style='Custom.TButton' # 应用自定义按钮样式 ) def set_info_type(self, info_type): """设置信息类型并更新按钮样式""" self.info_type_var.set(info_type) self.update_button_style(info_type) self.info_type_changed() def info_type_changed(self): """当信息类型变更时的处理""" info_type = self.info_type_var.get() self.status.config(text=f"已切换至: {info_type}") # 关闭已有的详细窗口 if self.detail_window and self.detail_window.window.winfo_exists(): self.detail_window.window.destroy() self.detail_window = None # 根据选择的信息类型切换中间面板 if info_type == "队列信息": # 隐藏资源监控卡片,显示队列监控卡片 self.resource_card.pack_forget() self.queue_card.container.pack(fill=tk.BOTH, expand=True) else: # 隐藏队列监控卡片,显示资源监控卡片 self.queue_card.container.pack_forget() self.resource_card.pack(fill=tk.BOTH, expand=True) # 根据选择的信息类型显示详细窗口 if info_type == "MLU信息" and self.last_data: self.show_detail_window("MLU核心利用率详情", self.last_data['mlu_usage'], self.last_data['mlu_cores'], "MLU") elif info_type == "CPU信息" and self.last_data: self.show_detail_window("CPU核心利用率详情", self.last_data['cpu_usage'], self.last_data['cpu_cores'], "CPU") def show_detail_window(self, title, avg_usage, core_usages, core_type): """显示详细核心利用率窗口""" # 关闭已有的详细窗口 if self.detail_window and self.detail_window.window.winfo_exists(): self.detail_window.window.destroy() # 创建新窗口 self.detail_window = DetailWindow(self.root, title, avg_usage, core_usages, core_type) def data_received(self, data): """从MLUDataReceiver接收数据的回调函数""" if not self.monitoring_active: return # 更新状态指示器 if data['status'] == "在线": self.status_indicator.config(text="● 已连接", bootstyle=(SUCCESS, INVERSE)) else: self.status_indicator.config(text="● 设备离线", bootstyle=(DANGER, INVERSE)) # 保存最新数据 self.last_data = data # 使用after安全更新UI self.root.after(0, self.update_ui, data) def start_monitoring(self): """启动资源监控""" if self.monitoring_active: return self.status.config(text="启动系统资源监控...") self.monitoring_active = True self.status_indicator.config(text="● 接收数据中...", bootstyle=(INFO, INVERSE)) def stop_monitoring(self): """停止资源监控""" self.monitoring_active = False self.status.config(text="监控已暂停") self.status_indicator.config(text="● 监控暂停", bootstyle=(WARNING, INVERSE)) def reset_all(self): """重置所有监控指标""" for bar in self.progress_bars: bar.set_value(0) self.status.config(text="所有监控指标已重置") def change_theme(self): """更改应用主题""" theme = self.theme_var.get() # 正确切换主题方法 self.root.style.theme_use(theme) self.status.config(text=f"主题已切换为: {theme.capitalize()}") # 更新所有进度条的颜色 style = tb.Style() for bar in self.progress_bars: bar.text_color = style.colors.fg bar.bg_color = style.colors.bg bar.secondary_bg = style.colors.inputbg bar.canvas.config(bg=bar.bg_color) bar.canvas.itemconfig(bar.text_id, fill=bar.text_color) bar.canvas.itemconfig(bar.title_id, fill=bar.text_color) bar.draw_background() bar.update_display() def update_ui(self, data): """安全更新UI组件(在主线程执行)""" # 更新进度条的动画速度 speed = self.animation_speed.get() for bar in self.progress_bars: bar.animation_speed = speed # 更新进度条 self.progress_bars[0].set_value(data['cpu_temp']) self.progress_bars[1].set_value(data['power']) self.progress_bars[2].set_value(data['mem_usage']) self.progress_bars[3].set_value(data['bandwidth']) self.progress_bars[4].set_value(data['mlu_usage']) self.progress_bars[5].set_value(data['cpu_usage']) # 更新实时状态标签 self.realtime_labels["cpu_temp"].config(text=f"{data['cpu_temp']:.1f}") self.realtime_labels["power"].config(text=f"{data['power']:.1f}") self.realtime_labels["memory"].config(text=f"{data['mem_usage']:.1f}") self.realtime_labels["bandwidth"].config(text=f"{data['bandwidth']:.1f}") self.realtime_labels["mlu_usage"].config(text=f"{data['mlu_usage']:.1f}") self.realtime_labels["cpu_usage"].config(text=f"{data['cpu_usage']:.1f}") # 更新核心利用率标签 for i in range(4): self.core_labels[f"cpu_core_{i}"].config(text=f"{data['cpu_cores'][i]:.1f}%") self.core_labels[f"mlu_core_{i}"].config(text=f"{data['mlu_cores'][i]:.1f}%") # 更新状态栏 info_type = self.info_type_var.get() status_text = ( f"当前设备: MLU220 | " f"信息类型: {info_type} | " f"状态: {data['status']} | " f"CPU: {data['cpu_usage']:.1f}% | " f"温度: {data['cpu_temp']:.1f}°C | " f"内存: {data['mem_usage']:.1f}% | " f"MLU: {data['mlu_usage']:.1f}%" ) self.status.config(text=status_text) # 如果详细窗口存在且是当前选择的信息类型,更新详细窗口 current_type = self.info_type_var.get() if self.detail_window and self.detail_window.window.winfo_exists(): if current_type == "MLU信息": self.detail_window.update_values( data['mlu_usage'], data['mlu_cores'] ) elif current_type == "CPU信息": self.detail_window.update_values( data['cpu_usage'], data['cpu_cores'] ) def on_closing(self): """窗口关闭时的清理操作""" if hasattr(self, 'data_receiver'): self.data_receiver.stop() # 停止队列监控 if hasattr(self, 'queue_card'): self.queue_card.stop() self.root.destroy() if __name__ == "__main__": app = SystemMonitorApp()修改之后出现了错误,帮我改修,写出完整的修改代码
07-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值