import pandas as pd
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import json
import openpyxl
from openpyxl.utils.dataframe import dataframe_to_rows
from tkinter.font import Font
import traceback
import logging
import sys
import io
import numpy as np
from PIL import Image, ImageTk
# 配置日志系统
logging.basicConfig(
filename='app.log',
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger()
class ScrollableFrame(ttk.Frame):
"""自定义可滚动框架实现"""
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
# 创建Canvas和滚动条
self.canvas = tk.Canvas(self, highlightthickness=0)
self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.scrollable_frame = ttk.Frame(self.canvas)
# 配置Canvas
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas_frame = self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
# 布局
self.canvas.pack(side="left", fill="both", expand=True, padx=0, pady=0)
self.scrollbar.pack(side="right", fill="y")
# 绑定事件
self.scrollable_frame.bind("<Configure>", self.on_frame_configure)
self.canvas.bind("<Configure>", self.on_canvas_configure)
self.canvas.bind_all("<MouseWheel>", self.on_mousewheel)
def on_frame_configure(self, event):
"""当内部框架大小改变时更新滚动区域"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def on_canvas_configure(self, event):
"""当Canvas大小改变时调整内部框架宽度"""
self.canvas.itemconfig(self.canvas_frame, width=event.width)
def on_mousewheel(self, event):
"""鼠标滚轮滚动支持"""
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
class ToolTip:
"""自定义工具提示类"""
def __init__(self, widget, text='', max_width=500):
self.widget = widget
self.text = text
self.max_width = max_width
self.tip_window = None
self.id = None
self.x = self.y = 0
def show_tip(self, text, x, y):
"""显示工具提示"""
self.text = text
if self.tip_window or not self.text:
return
# 计算屏幕位置
x = x + self.widget.winfo_rootx() + 20
y = y + self.widget.winfo_rooty() + 20
# 创建顶层窗口
self.tip_window = tk.Toplevel(self.widget)
self.tip_window.wm_overrideredirect(True)
self.tip_window.wm_geometry(f"+{x}+{y}")
# 创建标签
label = tk.Label(self.tip_window, text=self.text, justify='left',
background="#ffffe0", relief='solid', borderwidth=1,
wraplength=self.max_width)
label.pack(ipadx=1, ipady=1)
def hide_tip(self):
"""隐藏工具提示"""
if self.tip_window:
self.tip_window.destroy()
self.tip_window = None
class ExcelControlPanel:
def __init__(self, master):
self.master = master
master.title("功能点确认系统")
master.geometry("1280x800")
master.configure(bg="#f0f2f5")
# 设置全局样式
self.set_styles()
# 加载配置
self.config = self.load_config()
# 初始化多列确认配置
self.confirmation_columns = self.config.get("confirmation_columns", [])
self.column_order = self.config.get("column_order", [])
# 创建界面元素
self.create_widgets()
# 初始化数据
self.excel_path = ""
self.df = None
self.check_states = {} # 存储每个功能点的复选框状态
self.current_sheet = ""
self.header_row = 0
self.full_descriptions = {} # 存储完整的功能点描述
self.tooltip = None # 工具提示实例
# 更新列选择器
self.update_col_selector()
def set_styles(self):
"""设置全局样式和字体"""
style = ttk.Style()
style.theme_use('clam')
# 自定义字体
self.title_font = Font(family="Microsoft YaHei", size=16, weight="bold")
self.subtitle_font = Font(family="Microsoft YaHei", size=10)
self.normal_font = Font(family="Microsoft YaHei", size=9)
# 配置样式
style.configure("TFrame", background="#f0f2f5")
style.configure("TLabel", font=self.normal_font, background="#f0f2f5", foreground="#333")
style.configure("TButton", font=self.normal_font, padding=8)
# Treeview样式 - 增加行高以容纳更多内容
style.configure("Treeview.Heading", font=self.subtitle_font, background="#4a76b5", foreground="white")
style.configure("Treeview", font=self.normal_font, rowheight=50, background="white", fieldbackground="white")
# 添加交替行背景色
style.configure("evenrow.Treeview", background="#f8f9fa")
style.configure("oddrow.Treeview", background="white")
# 状态栏样式
style.configure("Status.TFrame", background="#e0e0e0")
style.configure("Status.TLabel",
font=self.normal_font,
background="#4a76b5",
foreground="#ffffff",
padding=5)
# 卡片样式
style.configure("Card.TFrame", background="white", borderwidth=0, relief="solid",
padding=10, bordercolor="#e1e4e8")
style.configure("Card.TLabelframe", background="white", borderwidth=1, relief="solid",
padding=10, bordercolor="#e1e4e8")
style.configure("Card.TLabelframe.Label", font=self.subtitle_font, foreground="#2c3e50",
background="white")
# 按钮样式
style.map("Primary.TButton",
background=[("active", "#3a66a5"), ("pressed", "#2a5685")],
foreground=[("active", "white"), ("pressed", "white")])
style.configure("Primary.TButton", background="#4a76b5", foreground="white",
font=self.subtitle_font, borderwidth=0)
style.map("Success.TButton",
background=[("active", "#28a745"), ("pressed", "#218838")],
foreground=[("active", "white"), ("pressed", "white")])
style.configure("Success.TButton", background="#28a745", foreground="white",
font=self.subtitle_font, borderwidth=0)
style.map("Danger.TButton",
background=[("active", "#dc3545"), ("pressed", "#c82333")],
foreground=[("active", "white"), ("pressed", "white")])
style.configure("Danger.TButton", background="#dc3545", foreground="white",
font=self.subtitle_font, borderwidth=0)
# 输入框样式
style.configure("Custom.TEntry", fieldbackground="#f8f9fa", bordercolor="#ced4da")
def load_config(self):
"""加载配置文件"""
config_path = "excel_config.json"
default_config = {
"id_col": "No.",
"desc_col": "レビュー観点(CHN)",
"status_col": "レビュー結果",
"sheet_name": "",
"header_row": 9,
"last_dir": os.getcwd(),
"custom_status": "OK",
"confirmation_columns": [],
"column_order": ["selection", "id", "description", "status"],
"max_desc_length": 300 # 新增:最大描述长度
}
if os.path.exists(config_path):
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
# 确保所有键都存在
for key in default_config:
if key not in config:
config[key] = default_config[key]
return config
except Exception as e:
logger.error(f"加载配置文件失败: {str(e)}")
return default_config
return default_config
def save_config(self):
"""保存配置文件"""
config_path = "excel_config.json"
try:
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(self.config, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"保存配置文件失败: {str(e)}")
def create_widgets(self):
"""创建现代化界面元素"""
# 主容器
main_container = ttk.Frame(self.master, style="Card.TFrame")
main_container.pack(fill="both", expand=True, padx=20, pady=20)
# 标题栏
title_frame = ttk.Frame(main_container, style="Title.TFrame")
title_frame.pack(fill="x", pady=(0, 20))
ttk.Label(title_frame, text="功能点确认系统", font=self.title_font,
background="#4a76b5", foreground="white", padding=10).pack(side="left", fill="x", expand=True)
# 主内容区域
content_frame = ttk.Frame(main_container)
content_frame.pack(fill="both", expand=True)
# 左侧控制面板 - 可滚动
control_container = ttk.Frame(content_frame, width=350)
control_container.pack(side="left", fill="y", padx=(0, 20))
# 创建自定义可滚动框架
scrollable_frame = ScrollableFrame(control_container, width=350)
scrollable_frame.pack(fill="both", expand=True)
# 获取内部框架
inner_frame = scrollable_frame.scrollable_frame
# 美化控制面板
control_card = ttk.LabelFrame(inner_frame, text="控制面板", style="Card.TLabelframe")
control_card.pack(fill="both", expand=True, padx=10, pady=10)
# 文件选择区域
file_frame = ttk.LabelFrame(control_card, text="Excel文件设置")
file_frame.pack(fill="x", padx=10, pady=(0, 15))
ttk.Label(file_frame, text="Excel文件路径:").pack(anchor="w", pady=(0, 5), padx=10)
path_frame = ttk.Frame(file_frame)
path_frame.pack(fill="x", pady=5, padx=10)
self.path_entry = ttk.Entry(path_frame, width=30, style="Custom.TEntry")
self.path_entry.pack(side="left", fill="x", expand=True, padx=(0, 5))
ttk.Button(path_frame, text="浏览", command=self.browse_file, width=8).pack(side="left")
ttk.Button(file_frame, text="加载数据", command=self.load_data, style="Primary.TButton").pack(fill="x", pady=10, padx=10)
# Sheet选择区域
sheet_frame = ttk.LabelFrame(control_card, text="工作表设置")
sheet_frame.pack(fill="x", padx=10, pady=(0, 15))
ttk.Label(sheet_frame, text="当前Sheet:").pack(anchor="w", padx=10, pady=(10, 0))
self.sheet_var = tk.StringVar(value=self.config.get("sheet_name", "未选择"))
sheet_display = ttk.Label(sheet_frame, textvariable=self.sheet_var, font=self.subtitle_font)
sheet_display.pack(anchor="w", pady=(0, 10), padx=10)
ttk.Button(sheet_frame, text="选择工作表", command=self.select_sheet, style="Primary.TButton").pack(fill="x", padx=10, pady=(0, 10))
# 表头行设置
header_frame = ttk.LabelFrame(control_card, text="表头设置")
header_frame.pack(fill="x", padx=10, pady=(0, 15))
ttk.Label(header_frame, text="表头所在行号:").pack(anchor="w", padx=10, pady=(10, 0))
self.header_var = tk.IntVar(value=self.config.get("header_row", 9))
ttk.Entry(header_frame, textvariable=self.header_var, width=10, style="Custom.TEntry").pack(anchor="w", pady=5, padx=10)
# 列名配置区域
col_frame = ttk.LabelFrame(control_card, text="列名配置")
col_frame.pack(fill="x", padx=10, pady=(0, 15))
# ID列
ttk.Label(col_frame, text="ID列名:").pack(anchor="w", padx=10, pady=(10, 0))
self.id_col_var = tk.StringVar(value=self.config["id_col"])
ttk.Entry(col_frame, textvariable=self.id_col_var, style="Custom.TEntry").pack(fill="x", pady=(0, 10), padx=10)
# 功能点列
ttk.Label(col_frame, text="功能点列名:").pack(anchor="w", padx=10, pady=(0, 0))
self.desc_col_var = tk.StringVar(value=self.config["desc_col"])
ttk.Entry(col_frame, textvariable=self.desc_col_var, style="Custom.TEntry").pack(fill="x", pady=(0, 10), padx=10)
# 状态列
ttk.Label(col_frame, text="状态列名:").pack(anchor="w", padx=10, pady=(0, 0))
self.status_col_var = tk.StringVar(value=self.config["status_col"])
ttk.Entry(col_frame, textvariable=self.status_col_var, style="Custom.TEntry").pack(fill="x", pady=(0, 10), padx=10)
# 自定义状态
ttk.Label(col_frame, text="自定义确认状态:").pack(anchor="w", padx=10, pady=(0, 0))
self.custom_status_var = tk.StringVar(value=self.config.get("custom_status", "OK"))
ttk.Entry(col_frame, textvariable=self.custom_status_var, style="Custom.TEntry").pack(fill="x", pady=(0, 10), padx=10)
# 描述长度设置
ttk.Label(col_frame, text="最大描述长度:").pack(anchor="w", padx=10, pady=(0, 0))
self.max_desc_var = tk.IntVar(value=self.config.get("max_desc_length", 300))
ttk.Entry(col_frame, textvariable=self.max_desc_var, style="Custom.TEntry").pack(fill="x", pady=(0, 10), padx=10)
# 多列确认设置
multi_col_frame = ttk.LabelFrame(control_card, text="多列确认设置")
multi_col_frame.pack(fill="x", padx=10, pady=(0, 15))
# 列选择器
ttk.Label(multi_col_frame, text="选择要确认的列:").pack(anchor="w", padx=10, pady=(10, 0))
col_selector_frame = ttk.Frame(multi_col_frame)
col_selector_frame.pack(fill="x", pady=5, padx=10)
self.col_selector = ttk.Combobox(col_selector_frame, state="readonly", width=15)
self.col_selector.pack(side="left", fill="x", expand=True, padx=(0, 5))
# 添加/移除按钮
ttk.Button(col_selector_frame, text="添加", command=self.add_confirmation_column, width=8).pack(side="left")
# 已选列列表
ttk.Label(multi_col_frame, text="已选确认列:").pack(anchor="w", padx=10, pady=(10, 0))
self.selected_cols_listbox = tk.Listbox(multi_col_frame, height=3, font=self.normal_font,
bg="#f8f9fa", highlightthickness=0)
self.selected_cols_listbox.pack(fill="x", pady=5, padx=10)
# 加载已配置的确认列
for col in self.confirmation_columns:
self.selected_cols_listbox.insert(tk.END, col)
# 移除按钮
remove_btn = ttk.Button(multi_col_frame, text="移除选中列", command=self.remove_confirmation_column)
remove_btn.pack(fill="x", pady=(0, 10), padx=10)
# 操作按钮区域
btn_frame = ttk.Frame(control_card)
btn_frame.pack(fill="x", padx=5, pady=10)
ttk.Button(btn_frame, text="保存配置", command=self.save_current_config, style="Success.TButton").pack(fill="x", pady=5)
ttk.Button(btn_frame, text="确认选中项", command=self.confirm_selected, style="Primary.TButton").pack(fill="x", pady=5)
ttk.Button(btn_frame, text="全选", command=self.select_all, style="Primary.TButton").pack(side="left", fill="x", expand=True, pady=5)
ttk.Button(btn_frame, text="取消全选", command=self.deselect_all, style="Danger.TButton").pack(side="left", fill="x", expand=True, pady=5)
ttk.Button(btn_frame, text="保存到Excel", command=self.save_to_excel, style="Success.TButton").pack(fill="x", pady=5)
# 数据显示区域
data_card = ttk.LabelFrame(content_frame, text="功能点列表", style="Card.TLabelframe")
data_card.pack(side="right", fill="both", expand=True)
# Treeview容器
tree_frame = ttk.Frame(data_card)
tree_frame.pack(fill="both", expand=True, padx=5, pady=5)
# 创建Treeview
self.tree = ttk.Treeview(tree_frame, columns=[], show="headings", height=20)
# 滚动条设置
vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
# 使用grid布局管理器
self.tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
# 配置网格权重
tree_frame.columnconfigure(0, weight=1)
tree_frame.rowconfigure(0, weight=1)
# 添加标签样式 - 使用交替行背景色
self.tree.tag_configure("evenrow", background="#f8f9fa")
self.tree.tag_configure("oddrow", background="white")
self.tree.tag_configure("confirmed", background="#d4edda")
self.tree.tag_configure("pending", background="#f8d7da")
# 绑定列调整事件
self.tree.bind("<Configure>", self.on_tree_configure)
# 绑定点击事件处理复选框
self.tree.bind("<Button-1>", self.on_tree_click)
# 绑定选择事件
self.tree.bind("<<TreeviewSelect>>", self.on_tree_select)
# 绑定鼠标移动事件用于显示工具提示
self.tree.bind("<Motion>", self.on_tree_motion)
self.tree.bind("<Leave>", self.on_tree_leave)
# 详情区域
detail_frame = ttk.LabelFrame(data_card, text="功能点详情", style="Card.TLabelframe")
detail_frame.pack(fill="x", padx=5, pady=(0, 5))
self.detail_text = tk.Text(detail_frame, wrap="word", font=self.normal_font, height=3,
bg="#f8f9fa", relief="solid", borderwidth=1)
scroll_detail = ttk.Scrollbar(detail_frame, command=self.detail_text.yview)
self.detail_text.config(yscrollcommand=scroll_detail.set)
# 使用grid布局文本框和滚动条
self.detail_text.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
scroll_detail.grid(row=0, column=1, sticky="ns", pady=5)
# 配置网格权重
detail_frame.columnconfigure(0, weight=1)
detail_frame.rowconfigure(0, weight=1)
self.detail_text.config(state="disabled")
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪 - 请选择Excel文件开始")
# 创建状态栏容器框架
status_container = ttk.Frame(self.master, style="Status.TFrame")
status_container.pack(side="bottom", fill="x", padx=0, pady=0)
# 状态标签
status_bar = ttk.Label(
status_container,
textvariable=self.status_var,
style="Status.TLabel",
anchor="w",
padding=(10, 5, 10, 5)
)
status_bar.pack(side="bottom", fill="x", expand=False)
# 初始化Treeview列
self.update_treeview_columns()
# 创建工具提示
self.tooltip = ToolTip(self.tree)
def update_treeview_columns(self):
"""更新Treeview列"""
# 基础列
base_columns = ["selection", "id", "description", "status"]
# 所有列 = 基础列 + 确认列
all_columns = base_columns + self.confirmation_columns
# 如果列顺序未设置或长度不匹配,则重置
if not self.column_order or len(self.column_order) != len(all_columns):
self.column_order = all_columns
# 重新配置Treeview
self.tree.configure(columns=self.column_order)
# 设置列标题
column_titles = {
"selection": "选择",
"id": "ID",
"description": "功能点",
"status": "状态"
}
for col in self.column_order:
if col in column_titles:
self.tree.heading(col, text=column_titles[col])
else:
self.tree.heading(col, text=col)
# 设置列属性
if col == "selection":
self.tree.column(col, width=50, anchor="center")
elif col == "id":
self.tree.column(col, width=100, anchor="center")
elif col == "description":
self.tree.column(col, width=400, anchor="w", stretch=True)
elif col == "status":
self.tree.column(col, width=100, anchor="center")
else: # 确认列
self.tree.column(col, width=100, anchor="center")
# 调整列宽
self.adjust_columns()
def on_tree_configure(self, event):
"""Treeview大小改变时调整列宽"""
self.adjust_columns()
def on_tree_select(self, event):
"""当Treeview中选中行时,显示功能点详情"""
selected_items = self.tree.selection()
if not selected_items:
return
item = selected_items[0]
# 获取完整功能点描述
full_desc = self.full_descriptions.get(item, "无描述")
self.detail_text.config(state="normal")
self.detail_text.delete(1.0, tk.END)
self.detail_text.insert(tk.END, full_desc)
self.detail_text.config(state="disabled")
def on_tree_motion(self, event):
"""当鼠标在Treeview上移动时,显示工具提示"""
region = self.tree.identify("region", event.x, event.y)
if region == "cell":
column = self.tree.identify_column(event.x)
item = self.tree.identify_row(event.y)
if not item:
return
# 获取列索引
col_idx = int(column[1:]) - 1
columns = self.tree["columns"]
# 确保索引有效
if col_idx >= len(columns):
return
# 获取列标识符
col_id = columns[col_idx]
# 只处理"功能点"列
if self.tree.heading(col_id, "text") == "功能点":
# 获取完整描述
full_desc = self.full_descriptions.get(item, "")
# 如果描述很长,显示工具提示
if full_desc and len(full_desc) > self.config.get("max_desc_length", 300):
self.tooltip.show_tip(full_desc, event.x, event.y)
def on_tree_leave(self, event):
"""当鼠标离开Treeview时,隐藏工具提示"""
self.tooltip.hide_tip()
def on_tree_click(self, event):
"""处理Treeview点击事件,切换复选框状态"""
region = self.tree.identify("region", event.x, event.y)
if region == "cell":
column = self.tree.identify_column(event.x)
item = self.tree.identify_row(event.y)
if not item:
return
# 获取列索引
col_idx = int(column[1:]) - 1
columns = self.tree["columns"]
# 确保索引有效
if col_idx >= len(columns):
return
# 获取列标识符
col_id = columns[col_idx]
# 只处理"选择"列
if self.tree.heading(col_id, "text") == "选择":
# 获取当前状态
values = list(self.tree.item(item, "values"))
current_state = values[col_idx]
# 切换状态
if current_state == "☐":
new_state = "☑"
else:
new_state = "☐"
# 更新Treeview
values[col_idx] = new_state
self.tree.item(item, values=values)
# 更新状态存储
self.check_states[item] = (new_state == "☑")
# 更新行的整体状态显示
self.update_row_status(item)
def update_row_status(self, item_id):
"""根据复选框状态更新行状态显示"""
values = list(self.tree.item(item_id, "values"))
is_checked = self.check_states.get(item_id, False)
# 找到状态列的位置
status_col_idx = None
for idx, col_id in enumerate(self.tree["columns"]):
if self.tree.heading(col_id, "text") == "状态":
status_col_idx = idx
break
if status_col_idx is not None and status_col_idx < len(values):
if is_checked:
values[status_col_idx] = "✓ 待确认"
else:
values[status_col_idx] = "✗ 未确认"
self.tree.item(item_id, values=values)
def adjust_columns(self, event=None):
"""根据窗口大小自动调整列宽"""
if not self.tree.winfo_exists():
return
width = self.tree.winfo_width()
if width < 100: # 防止宽度过小
return
# 计算可用宽度
available_width = width - 20 # 减去滚动条宽度
# 设置列宽比例 - 增加功能点列的比例
column_weights = {
"selection": 0.05,
"id": 0.10,
"description": 0.65,
"status": 0.08
}
# 设置基础列宽
for col, weight in column_weights.items():
if col in self.tree["columns"]:
col_width = int(available_width * weight)
self.tree.column(col, width=col_width)
# 设置确认列宽
if self.confirmation_columns:
confirm_col_width = int(available_width * 0.06) # 减小确认列宽度
for col in self.confirmation_columns:
if col in self.tree["columns"]:
self.tree.column(col, width=confirm_col_width)
def update_col_selector(self):
"""更新列选择器"""
if self.df is not None:
self.col_selector["values"] = list(self.df.columns)
def add_confirmation_column(self):
"""添加确认列"""
col = self.col_selector.get()
if col and col not in self.confirmation_columns:
self.confirmation_columns.append(col)
self.selected_cols_listbox.insert(tk.END, col)
self.update_treeview_columns()
def remove_confirmation_column(self):
"""移除确认列"""
selection = self.selected_cols_listbox.curselection()
if selection:
index = selection[0]
self.confirmation_columns.pop(index)
self.selected_cols_listbox.delete(index)
self.update_treeview_columns()
def browse_file(self):
initial_dir = self.config.get("last_dir", os.getcwd())
file_path = filedialog.askopenfilename(
initialdir=initial_dir,
filetypes=[("Excel文件", "*.xlsx;*.xls")]
)
if file_path:
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, file_path)
# 更新最后访问目录
self.config["last_dir"] = os.path.dirname(file_path)
self.save_config()
def select_sheet(self):
"""选择工作表"""
file_path = self.path_entry.get()
if not file_path or not os.path.exists(file_path):
messagebox.showerror("错误", "请先选择有效的Excel文件")
return
try:
# 获取所有sheet名称
xl = pd.ExcelFile(file_path)
sheet_names = xl.sheet_names
# 创建选择对话框
sheet_dialog = tk.Toplevel(self.master)
sheet_dialog.title("选择工作表")
sheet_dialog.geometry("400x300")
sheet_dialog.transient(self.master)
sheet_dialog.grab_set()
sheet_dialog.configure(bg="#f5f7fa")
ttk.Label(sheet_dialog, text="请选择工作表:", font=self.subtitle_font, background="#f5f7fa").pack(pady=10)
# 使用Treeview显示工作表
sheet_tree = ttk.Treeview(sheet_dialog, columns=("名称",), show="headings", height=8)
sheet_tree.heading("名称", text="工作表名称")
sheet_tree.column("名称", width=350)
sheet_tree.pack(fill="both", expand=True, padx=20, pady=5)
for name in sheet_names:
sheet_tree.insert("", "end", values=(name,))
# 按钮框架
btn_frame = ttk.Frame(sheet_dialog)
btn_frame.pack(fill="x", padx=20, pady=10)
def on_select():
selected = sheet_tree.selection()
if selected:
self.current_sheet = sheet_tree.item(selected[0], "values")[0]
self.sheet_var.set(self.current_sheet)
# 保存工作表名称到配置
self.config["sheet_name"] = self.current_sheet
self.save_config()
sheet_dialog.destroy()
ttk.Button(btn_frame, text="取消", command=sheet_dialog.destroy).pack(side="right", padx=5)
ttk.Button(btn_frame, text="确定", command=on_select, style="Primary.TButton").pack(side="right")
except Exception as e:
messagebox.showerror("错误", f"读取Excel失败: {str(e)}")
logger.error(f"选择工作表失败: {str(e)}")
def load_data(self):
file_path = self.path_entry.get()
if not file_path or not os.path.exists(file_path):
messagebox.showerror("错误", "无效的文件路径")
return
# 在状态栏显示加载中
self.status_var.set("正在加载数据...")
self.master.update() # 强制更新界面
# 清空Treeview
for item in self.tree.get_children():
self.tree.delete(item)
self.check_states = {}
self.full_descriptions = {}
# 获取当前配置
id_col = self.id_col_var.get().strip()
desc_col = self.desc_col_var.get().strip()
status_col = self.status_col_var.get().strip()
sheet_name = self.sheet_var.get() or None
header_row = self.header_var.get() - 1 # pandas header是0-based索引
max_desc_length = self.max_desc_var.get() # 最大描述长度
# 直接调用加载任务
self.load_task(file_path, id_col, desc_col, status_col, sheet_name, header_row, max_desc_length)
def load_task(self, file_path, id_col, desc_col, status_col, sheet_name, header_row, max_desc_length):
try:
# 确保使用正确的sheet_name
if not sheet_name and self.config.get("sheet_name"):
sheet_name = self.config["sheet_name"] # 从配置获取工作表名
# 读取Excel文件
if sheet_name:
self.df = pd.read_excel(
file_path,
sheet_name=sheet_name,
header=header_row
)
else:
# 如果没有指定sheet,尝试读取第一个sheet
self.df = pd.read_excel(
file_path,
header=header_row
)
# 尝试获取第一个sheet的名称
xl = pd.ExcelFile(file_path)
if xl.sheet_names:
self.current_sheet = xl.sheet_names[0]
self.sheet_var.set(self.current_sheet)
self.excel_path = file_path
# 检查列是否存在
missing_cols = []
if id_col not in self.df.columns:
missing_cols.append(f"ID列 '{id_col}'")
if desc_col not in self.df.columns:
missing_cols.append(f"功能点列 '{desc_col}'")
if missing_cols:
# 提供更详细的错误信息
available_cols = "\n".join(self.df.columns)
error_msg = (
f"以下列不存在: {', '.join(missing_cols)}\n\n"
f"可用列名:\n{available_cols}\n\n"
"请检查表头行设置是否正确(默认为第9行)"
)
raise ValueError(error_msg)
# 如果状态列不存在,则创建
if status_col not in self.df.columns:
self.df[status_col] = "否" # 默认未确认
# 更新列选择器
self.update_col_selector()
# 更新Treeview列
self.update_treeview_columns()
# 修复ID列显示问题 - 更健壮的处理
if id_col in self.df.columns:
# 处理所有可能的缺失值
self.df[id_col] = self.df[id_col].fillna("")
# 处理浮点数和整数类型
if self.df[id_col].dtype == float:
# 将浮点数转换为整数再转字符串
self.df[id_col] = self.df[id_col].astype(int).astype(str)
elif self.df[id_col].dtype == int:
self.df[id_col] = self.df[id_col].astype(str)
else:
# 确保是字符串类型
self.df[id_col] = self.df[id_col].astype(str)
# 替换可能的NaN字符串和空字符串
self.df[id_col] = self.df[id_col].replace(['nan', 'NaN', 'NaT', ''], '缺失ID')
# 处理描述列显示问题
if desc_col in self.df.columns:
# 填充缺失值
self.df[desc_col] = self.df[desc_col].fillna("无描述")
# 确保是字符串类型
self.df[desc_col] = self.df[desc_col].astype(str)
# 添加换行符使长文本自动换行
max_length = 100 # 每100个字符换行
self.df[desc_col] = self.df[desc_col].apply(
lambda x: '\n'.join([x[i:i+max_length] for i in range(0, len(x), max_length)]))
# 添加数据到Treeview
for i, row in self.df.iterrows():
status_value = row.get(status_col, "否")
# 使用图标表示状态
status_icon = "✓" if status_value in ["是", "Y", "y", "Yes", "yes", "OK", "确认"] else "✗"
status_text = f"{status_icon} {status_value}"
# 获取ID值
id_value = str(row[id_col]) if id_col in row else "缺失ID"
# 获取完整描述
full_desc = row[desc_col] if desc_col in row else "无描述"
# 创建显示描述(截断过长的描述)
display_desc = full_desc
if len(full_desc) > max_desc_length:
display_desc = full_desc[:max_desc_length] + "..." # 添加省略号
# 构建行数据字典
row_data = {
"selection": "☐",
"id": id_value,
"description": display_desc,
"status": status_text
}
# 添加多列确认数据
for col in self.confirmation_columns:
row_data[col] = row[col] if col in row else ""
# 按列顺序构建值列表
ordered_values = [row_data.get(col, "") for col in self.column_order]
# 确定行样式标签 - 交替行背景色
row_tag = "evenrow" if i % 2 == 0 else "oddrow"
status_tag = "confirmed" if status_icon == "✓" else "pending"
tags = (row_tag, status_tag)
# 插入行
item_id = self.tree.insert("", "end", values=ordered_values, tags=tags)
# 存储完整描述
self.full_descriptions[item_id] = full_desc
# 存储复选框状态
self.check_states[item_id] = (status_icon == "✓")
# 每100行更新一次界面
if i % 100 == 0:
self.tree.update()
self.master.update_idletasks()
# 更新状态
self.status_var.set(f"成功加载: {len(self.df)} 条记录")
# 调整列宽并刷新界面
self.adjust_columns()
except Exception as e:
# 显示详细的错误信息
error_msg = f"读取Excel失败: {str(e)}"
self.status_var.set("加载失败")
messagebox.showerror("加载错误", error_msg)
logger.error(f"加载数据失败: {error_msg}\n{traceback.format_exc()}")
def confirm_selected(self):
"""确认选中的功能点"""
selected_items = []
for item_id in self.tree.get_children():
if self.check_states.get(item_id, False):
selected_items.append(item_id)
if not selected_items:
messagebox.showinfo("提示", "请先选择功能点")
return
custom_status = self.custom_status_var.get().strip() or "OK"
for item_id in selected_items:
values = list(self.tree.item(item_id, "values"))
# 找到状态列的位置
status_col_idx = None
for idx, col_id in enumerate(self.tree["columns"]):
if self.tree.heading(col_id, "text") == "状态":
status_col_idx = idx
break
# 更新状态列
if status_col_idx is not None and status_col_idx < len(values):
values[status_col_idx] = f"✓ {custom_status}"
# 更新多列确认
if self.confirmation_columns and self.df is not None:
row_idx = self.tree.index(item_id)
for col in self.confirmation_columns:
if col in self.df.columns:
# 找到列在Treeview中的位置
col_idx = self.column_order.index(col)
if col_idx < len(values):
values[col_idx] = custom_status
self.df.at[row_idx, col] = custom_status
# 更新Treeview
self.tree.item(item_id, values=values)
self.status_var.set(f"已确认 {len(selected_items)} 个功能点")
# 自动保存
self.auto_save()
def select_all(self):
"""全选功能点"""
for item_id in self.tree.get_children():
values = list(self.tree.item(item_id, "values"))
# 找到选择列的位置
select_col_idx = None
for idx, col_id in enumerate(self.tree["columns"]):
if self.tree.heading(col_id, "text") == "选择":
select_col_idx = idx
break
# 更新选择列
if select_col_idx is not None and select_col_idx < len(values):
values[select_col_idx] = "☑"
self.tree.item(item_id, values=values)
self.check_states[item_id] = True
self.update_row_status(item_id)
self.status_var.set("已全选所有功能点")
def deselect_all(self):
"""取消全选功能点"""
for item_id in self.tree.get_children():
values = list(self.tree.item(item_id, "values"))
# 找到选择列的位置
select_col_idx = None
for idx, col_id in enumerate(self.tree["columns"]):
if self.tree.heading(col_id, "text") == "选择":
select_col_idx = idx
break
# 更新选择列
if select_col_idx is not None and select_col_idx < len(values):
values[select_col_idx] = "☐"
self.tree.item(item_id, values=values)
self.check_states[item_id] = False
self.update_row_status(item_id)
self.status_var.set("已取消全选所有功能点")
def save_current_config(self):
"""保存当前配置"""
self.config["id_col"] = self.id_col_var.get().strip()
self.config["desc_col"] = self.desc_col_var.get().strip()
self.config["status_col"] = self.status_col_var.get().strip()
self.config["sheet_name"] = self.sheet_var.get()
self.config["header_row"] = self.header_var.get()
self.config["custom_status"] = self.custom_status_var.get().strip()
self.config["confirmation_columns"] = self.confirmation_columns
self.config["column_order"] = self.column_order
self.config["max_desc_length"] = self.max_desc_var.get() # 保存最大描述长度
self.save_config()
messagebox.showinfo("成功", "配置已保存")
def auto_save(self):
"""自动保存功能"""
if self.df is None or not self.excel_path:
return
try:
# 获取当前配置
id_col = self.id_col_var.get().strip()
desc_col = self.desc_col_var.get().strip()
status_col = self.status_col_var.get().strip()
# 更新DataFrame中的确认状态
for i, item_id in enumerate(self.tree.get_children()):
# 获取Treeview中的状态值
values = self.tree.item(item_id, "values")
# 找到状态列的位置
status_col_idx = None
for idx, col_id in enumerate(self.tree["columns"]):
if self.tree.heading(col_id, "text") == "状态":
status_col_idx = idx
break
if status_col_idx is not None and status_col_idx < len(values):
status_value = values[status_col_idx]
if status_value.startswith(("✓", "✗")):
status_value = status_value[2:].strip()
self.df.at[i, status_col] = status_value
# 保存回Excel
wb = openpyxl.load_workbook(self.excel_path)
if self.current_sheet in wb.sheetnames:
del wb[self.current_sheet]
ws = wb.create_sheet(self.current_sheet)
# 写入数据
for r, row in enumerate(dataframe_to_rows(self.df, index=False, header=True), 1):
ws.append(row)
wb.save(self.excel_path)
self.status_var.set("数据已自动保存")
except Exception as e:
self.status_var.set("自动保存失败")
logger.error(f"自动保存失败: {str(e)}")
def save_to_excel(self):
if self.df is None:
messagebox.showerror("错误", "没有加载的数据")
return
try:
# 执行保存
self.auto_save()
messagebox.showinfo("成功", f"数据已保存到:\n{self.excel_path}")
self.status_var.set("数据保存成功")
except Exception as e:
messagebox.showerror("保存错误", f"写入Excel失败: {str(e)}\n请确保文件未被其他程序打开")
logger.error(f"保存失败: {str(e)}")
def main():
root = tk.Tk()
# 设置应用图标
try:
# 创建一个简单的蓝色方块作为图标
icon_data = """
R0lGODlhEAAQAIAAAP///wAAACH5BAEAAAAALAAAAAAQABAAAAIOhI+py+0Po5y02ouzPgUAOw==
"""
icon_img = tk.PhotoImage(data=icon_data)
root.tk.call('wm', 'iconphoto', root._w, icon_img)
except Exception as e:
logger.warning(f"设置应用图标失败: {str(e)}")
# 添加全局异常处理
def handle_exception(exc_type, exc_value, exc_traceback):
error_msg = f"发生未捕获的异常:\n{exc_type.__name__}: {exc_value}"
tb_str = "".join(traceback.format_tb(exc_traceback))
logger.error(f"未捕获的异常: {error_msg}\n{tb_str}")
messagebox.showerror("严重错误", f"{error_msg}\n\n请查看日志获取详细信息")
root.destroy()
sys.excepthook = handle_exception
app = ExcelControlPanel(root)
root.mainloop()
if __name__ == "__main__":
# 强制使用UTF-8编码
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
main()
1、还是显示缺失ID,但实际上有ID,并且是整数
2、当鼠标点击到某一行时,虽然程序没有崩溃,但是会报以下警告Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 540, in on_tree_leave
self.tooltip.hide_tip()
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'hide_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 540, in on_tree_leave
self.tooltip.hide_tip()
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'hide_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 540, in on_tree_leave
self.tooltip.hide_tip()
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'hide_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "e:\system\Desktop\项目所需文件\工具\功能点确认工具\Excel_Function_Confirmation_System.py", line 536, in on_tree_motion
self.tooltip.show_tip(full_desc, event.x, event.y)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'show_tip'
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1968, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
最新发布