[Python] 纯文本查看 复制代码
# 导入所需的库
import tkinter as tk
from tkinter import ttk
import pandas as pd
import datetime
from pathlib import Path
from tkinter import messagebox
from datetime import timedelta
import ttkthemes
import threading
import time
# 创建一个名为Timer的类
class Timer:
def __init__(self, master):
# 初始化ThemedStyle
self.master = master
self.master.style = ttkthemes.ThemedStyle(master)
self.master.style.set_theme("adapta") # 设置主题为"adapta"
self.df = pd.DataFrame(columns=["位置", "怪物名称", "倒计时", "刷新计时", "刷新(分钟)", "击杀时间",
"备注信息"]) # 使用pandas创建一个DataFrame
self.load_data() # 加载数据
self.tree = ttk.Treeview(self.master, columns=(
"位置", "怪物名称", "倒计时", "刷新计时", "刷新(分钟)", "击杀时间", "备注信息"),
show="headings") # 创建一个Treeview对象
# 设置列宽
self.tree.column("位置", width=150, anchor="w")
self.tree.column("怪物名称", width=150, anchor="w")
self.tree.column("倒计时", width=150, anchor="center")
self.tree.column("刷新计时", width=150, anchor="center")
self.tree.column("刷新(分钟)", width=150, anchor="center")
self.tree.column("击杀时间", width=150, anchor="w")
self.tree.column("备注信息", width=300, anchor="w")
# 添加表头文字说明
self.tree.heading("位置", text="位置")
self.tree.heading("怪物名称", text="怪物名称")
self.tree.heading("倒计时", text="倒计时")
self.tree.heading("刷新计时", text="刷新计时")
self.tree.heading("刷新(分钟)", text="刷新(分钟)")
self.tree.heading("击杀时间", text="击杀时间")
self.tree.heading("备注信息", text="备注信息")
self.tree.pack() # 将Treeview添加到窗口
# 遍历DataFrame中的每一行,并将数据添加到Treeview中
for index, row in self.df.iterrows():
self.tree.insert("", "end", values=(
row["位置"], row["怪物名称"], row["倒计时"], row["刷新计时"], row["刷新(分钟)"], row["击杀时间"],
row["备注信息"]))
# 添加“添加计时”、“删除计时”和“编辑”按钮
add_button = tk.Button(self.master, text="添加计时", command=self.add_timer)
add_button.pack(side=tk.LEFT, padx=5)
edit_button = tk.Button(self.master, text="编辑计时", command=self.open_edit_dialog)
edit_button.pack(side=tk.LEFT, padx=5)
remove_button = tk.Button(self.master, text="删除计时", command=self.delete_timer)
remove_button.pack(side=tk.LEFT, padx=5)
self.selected_monster_name = None # 存储选中行的怪物名称
self.tree.bind("<ButtonRelease-1>", self.on_tree_select) # 为 Treeview 绑定单击事件
self.tree.bind("<Double-1>", self.on_double_click) # 为 Treeview 绑定双击事件
global active_countdown_threads # 存储当前正在运行的倒计时线程
active_countdown_threads = {}
def load_data(self):
data_file = Path('monster_timers.xlsx')
if data_file.exists():
self.df = pd.read_excel(data_file, engine='openpyxl', index_col=0, parse_dates=['刷新计时', '击杀时间'])
def save_data(self):
data_file = Path('monster_timers.xlsx')
self.df.to_excel(data_file, index_label='ID')
def on_tree_select(self, event):
selected_item = self.tree.selection()[0]
item_values = self.tree.item(selected_item, "values")
monster_name = item_values[0] # 获取选中行的“怪物名称”列值
def open_edit_dialog(self):
selected_item = self.tree.selection()
if selected_item: # 判断是否选中了树视图中的某一行
self.edit_timer(self.tree.item(selected_item[0], "values")[0]) # 传递选中行的怪物名称给edit_timer方法
else:
messagebox.showinfo("提示", "请先选择要编辑的计时记录。") # 显示提示信息,告知用户需先选中行
def add_timer(self):
add_dialog = tk.Toplevel(self.master)
add_dialog.title("添加计时")
monster_position_label = tk.Label(add_dialog, text="位置")
monster_position_label.grid(row=0, column=0)
monster_position_entry = tk.Entry(add_dialog)
monster_position_entry.grid(row=0, column=1)
monster_name_label = tk.Label(add_dialog, text="怪物名称")
monster_name_label.grid(row=1, column=0)
monster_name_entry = tk.Entry(add_dialog)
monster_name_entry.grid(row=1, column=1)
refresh_interval_label = tk.Label(add_dialog, text="刷新间隔")
refresh_interval_label.grid(row=2, column=0)
refresh_interval_entry = tk.Entry(add_dialog)
refresh_interval_entry.grid(row=2, column=1)
note_label = tk.Label(add_dialog, text="备注信息")
note_label.grid(row=3, column=0)
note_entry = tk.Entry(add_dialog)
note_entry.grid(row=3, column=1)
add_button = tk.Button(add_dialog, text="添加", command=lambda: self.add_row(monster_position_entry.get(),
monster_name_entry.get(),
int(refresh_interval_entry.get()),
note_entry.get()))
add_button.grid(row=4, column=0)
cancel_button = tk.Button(add_dialog, text="取消", command=add_dialog.destroy)
cancel_button.grid(row=4, column=1)
def add_row(self, monster_position, monster_name, refresh_interval, note):
# 确保新添加的行不含空或全NaN列
new_row = {
"位置": monster_position,
"怪物名称": monster_name,
"倒计时": "",
"刷新计时": "",
"刷新(分钟)": refresh_interval,
"击杀时间": " ",
"备注信息": note
}
# 移除值为None、NaN或空字符串的键值对
for key, value in list(new_row.items()):
if pd.isnull(value) or value in (None, ""):
del new_row[key]
# 创建新的数据行DataFrame,仅包含有效列
new_df_row = pd.DataFrame(new_row, index=[0])
# 检查新行DataFrame是否存在全NaN的列,并在必要时进行清理
while True:
has_all_nan_cols = False
for col in new_df_row.columns:
if new_df_row[col].isna().all():
new_df_row.drop(col, axis=1, inplace=True)
has_all_nan_cols = True
if not has_all_nan_cols:
break
# 合并新行与现有数据集
self.df = pd.concat([self.df, new_df_row], ignore_index=True, join='outer')
# 在 Treeview 中插入新行,使用格式化后的刷新计时值
self.tree.insert("", "end", values=(
new_row["位置"],
new_row["怪物名称"],
"",
"",
new_row["刷新(分钟)"],
"",
new_row["备注信息"]
))
# 保存数据至文件
self.save_data()
def edit_timer(self, monster_name):
selected_rows = self.df[self.df["怪物名称"] == monster_name].index.tolist()
if selected_rows:
selected_index = selected_rows[0]
selected_row = self.df.loc[selected_index]
edit_dialog = tk.Toplevel(self.master)
edit_dialog.title(f"编辑 {monster_name} 计时")
monster_position_label = tk.Label(edit_dialog, text="位置")
monster_position_label.grid(row=0, column=0)
monster_position_entry = tk.Entry(edit_dialog, textvariable=tk.StringVar(value=selected_row["位置"]))
monster_position_entry.grid(row=0, column=1)
monster_name_label = tk.Label(edit_dialog, text="怪物名称")
monster_name_label.grid(row=1, column=0)
monster_name_entry = tk.Entry(edit_dialog, textvariable=tk.StringVar(value=selected_row["怪物名称"]))
monster_name_entry.grid(row=1, column=1)
refresh_interval_label = tk.Label(edit_dialog, text="刷新(分钟)")
refresh_interval_label.grid(row=2, column=0)
refresh_interval_entry = tk.Entry(edit_dialog, textvariable=tk.StringVar(value=selected_row["刷新(分钟)"]))
refresh_interval_entry.grid(row=2, column=1)
note_label = tk.Label(edit_dialog, text="备注信息")
note_label.grid(row=3, column=0)
note_entry = tk.Entry(edit_dialog, textvariable=tk.StringVar(value=selected_row["备注信息"]))
note_entry.grid(row=3, column=1)
update_button = tk.Button(edit_dialog, text="更新", command=lambda: self.update_row(selected_index,
monster_position_entry.get(),
monster_name_entry.get(),
refresh_interval_entry.get(),
note_entry.get()))
update_button.grid(row=4, column=0)
cancel_button = tk.Button(edit_dialog, text="取消", command=edit_dialog.destroy)
cancel_button.grid(row=4, column=1)
def update_row(self, index, monster_position, monster_name, refresh_interval, note):
self.df.at[index, "位置"] = str(monster_position)
self.df.at[index, "怪物名称"] = str(monster_name)
self.df.at[index, "刷新(分钟)"] = int(refresh_interval)
self.df.at[index, "备注信息"] = str(note)
self.tree.item(self.tree.selection()[0], values=(self.df.loc[index, "位置"],
self.df.loc[index, "怪物名称"],
self.df.loc[index, "倒计时"],
self.df.loc[index, "刷新计时"],
self.df.loc[index, "刷新(分钟)"],
self.df.loc[index, "击杀时间"],
self.df.loc[index, "备注信息"]))
self.save_data()
def delete_timer(self):
selected_item = self.tree.selection()[0]
if selected_item:
item = self.tree.item(selected_item) # 获取选中项目的详细信息
item_values = item["values"] # 提取选中行的值
monster_name = item_values[0] # 获取选中行的“怪物名称”列值
if monster_name: # 检查 monster_name 是否非空
self.df = self.df[self.df["怪物名称"] != monster_name] # 删除对应行
self.tree.delete(selected_item) # 从 Treeview 中移除该行
# 添加以下缺失的代码:保存更新后的数据到文件
self.save_data()
def on_double_click(self, event):
global active_countdown_threads
selected_item = self.tree.selection()[0]
if selected_item:
# 获取选中行的索引
selected_index = self.tree.index(selected_item)
# 更改“倒计时”列的数据类型为 str
self.df["倒计时"] = self.df["倒计时"].astype(str)
# 记录当前时刻作为击杀时间
current_time = datetime.datetime.now()
# 计算刷新计时(假设 self.df.loc[selected_index, "刷新(分钟)"] 已经是整数)
refresh_minutes = int(self.df.loc[selected_index, "刷新(分钟)"])
refresh_delta = datetime.timedelta(minutes=refresh_minutes)
refresh_time = current_time + refresh_delta
# 计算剩余时间(以秒为单位)
remaining_time = refresh_minutes * 60
# 格式化击杀时间,仅显示到秒
formatted_current_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
# 格式化刷新计时,仅显示到秒
formatted_refresh_time = refresh_time.strftime('%Y-%m-%d %H:%M:%S')
# 更新 DataFrame 中的“刷新计时”列
self.df.at[selected_index, "刷新计时"] = formatted_refresh_time
# 更新 DataFrame 中的“击杀时间”列
self.df.at[selected_index, "击杀时间"] = formatted_current_time
# 停止该行已有的倒计时
if selected_item in active_countdown_threads:
self.stop_countdown(selected_item)
return
# 创建新的倒计时线程
countdown_thread = threading.Thread(target=self.countdown,
args=(selected_index, remaining_time, selected_item))
countdown_thread.start()
active_countdown_threads[selected_item] = {
"thread": countdown_thread,
"flag": True,
"after_id": None
}
# 更新 Treeview 显示的击杀时间
self.tree.item(selected_item, values=(self.df.loc[selected_index, "位置"],
self.df.loc[selected_index, "怪物名称"],
remaining_time,
self.df.loc[selected_index, "刷新计时"],
self.df.loc[selected_index, "刷新(分钟)"],
self.df.loc[selected_index, "击杀时间"],
self.df.loc[selected_index, "备注信息"]))
# 保存更新后的数据到文件
self.save_data()
def countdown(self, selected_index, remaining_time, selected_item):
global active_countdown_threads
def update_countdown(_remaining_time):
print(active_countdown_threads)
thread = active_countdown_threads.get(selected_item)
if thread and not thread["flag"]:
return
hours, remaining_seconds = divmod(_remaining_time, 3600)
minutes, seconds = divmod(remaining_seconds, 60)
self.df.at[selected_index, "倒计时"] = f"{hours:02}:{minutes:02}:{seconds:02}"
self.tree.item(selected_item, values=(self.df.loc[selected_index, "位置"],
self.df.loc[selected_index, "怪物名称"],
f"{hours:02}:{minutes:02}:{seconds:02}",
self.df.loc[selected_index, "刷新计时"],
self.df.loc[selected_index, "刷新(分钟)"],
self.df.loc[selected_index, "击杀时间"],
self.df.loc[selected_index, "备注信息"]))
if _remaining_time > 0:
if thread["after_id"]:
self.master.after_cancel(thread["after_id"])
thread["after_id"] = self.master.after(1000, update_countdown, _remaining_time - 1)
update_countdown(remaining_time)
# 倒计时结束后,移除该行的倒计时线程
# del self.active_countdown_threads[selected_item]
def stop_countdown(self, selected_item):
global active_countdown_threads
print("停止倒计时stop_countdown")
print(active_countdown_threads)
if selected_item in active_countdown_threads:
thread = active_countdown_threads.get(selected_item)
thread["flag"] = False
if thread["after_id"]:
self.master.after_cancel(thread["after_id"])
thread["after_id"] = None
thread["thread"].join()
print("线程已结束")
active_countdown_threads.pop(selected_item)
if __name__ == "__main__":
root = tk.Tk()
timer = Timer(root)
root.mainloop()