Merge pull request '新增了双击打开目录、文件的功能,实现了右键菜单' (#3) from dev into main

Reviewed-on: #3
This commit is contained in:
ahdoawhfo 2024-06-23 23:25:11 +08:00
commit f191ef6a1e
1 changed files with 106 additions and 62 deletions

View File

@ -10,36 +10,36 @@ import subprocess
class FolderScanner(threading.Thread): class FolderScanner(threading.Thread):
def __init__(self, start_path, queue, progress_queue): def __init__(self, start_path, queue, progress_queue):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.start_path = start_path self.start_path = start_path # 扫描路径
self.queue = queue self.queue = queue # 用于存储扫描结果
self.progress_queue = progress_queue self.progress_queue = progress_queue # 用于存储扫描进度
self.total_items = 0 self.total_items = 0 # 总共扫描的文件/目录数量
def run(self): def run(self): # 扫描文件/目录
self.total_items = sum( self.total_items = sum(
[len(files) + len(dirs) for r, dirs, files in os.walk(self.start_path)] [len(files) + len(dirs) for r, dirs, files in os.walk(self.start_path)]
) ) # 计算总共扫描的文件/目录数量
self.scan_folder(self.start_path) self.scan_folder(self.start_path) # 扫描文件/目录
self.queue.put(None) # Indicate that scanning is done self.queue.put(None) # 扫描结束
def scan_folder(self, folder): def scan_folder(self, folder): # 扫描文件/目录
scanned_items = 0 scanned_items = 0 # 已扫描的文件/目录数量
for dirpath, dirnames, filenames in os.walk(folder): for dirpath, dirnames, filenames in os.walk(folder): # 遍历文件/目录
for dirname in dirnames: for dirname in dirnames: # 遍历目录
folder_path = os.path.join(dirpath, dirname) folder_path = os.path.join(dirpath, dirname)
size = self.get_size(folder_path) size = self.get_size(folder_path)
self.queue.put((folder_path, size, dirpath, "folder")) self.queue.put((folder_path, size, dirpath, "folder"))
scanned_items += 1 scanned_items += 1
self.update_progress(scanned_items, folder_path) self.update_progress(scanned_items, folder_path)
for filename in filenames: for filename in filenames: # 遍历文件
file_path = os.path.join(dirpath, filename) file_path = os.path.join(dirpath, filename)
size = os.path.getsize(file_path) size = os.path.getsize(file_path)
self.queue.put((file_path, size, dirpath, "file")) self.queue.put((file_path, size, dirpath, "file"))
scanned_items += 1 scanned_items += 1
self.update_progress(scanned_items, file_path) self.update_progress(scanned_items, file_path)
def get_size(self, path): def get_size(self, path): # 获取文件/目录大小
total_size = 0 total_size = 0
for dirpath, dirnames, filenames in os.walk(path): for dirpath, dirnames, filenames in os.walk(path):
for f in filenames: for f in filenames:
@ -47,7 +47,7 @@ class FolderScanner(threading.Thread):
total_size += os.path.getsize(fp) total_size += os.path.getsize(fp)
return total_size return total_size
def update_progress(self, scanned_items, current_path): def update_progress(self, scanned_items, current_path): # 更新扫描进度
progress = (scanned_items / self.total_items) * 100 progress = (scanned_items / self.total_items) * 100
self.progress_queue.put( self.progress_queue.put(
(progress, scanned_items, self.total_items, current_path) (progress, scanned_items, self.total_items, current_path)
@ -55,7 +55,7 @@ class FolderScanner(threading.Thread):
class App(tk.Tk): class App(tk.Tk):
def __init__(self, start_path): def __init__(self, start_path): # 初始化
super().__init__() super().__init__()
self.withdraw() self.withdraw()
self.title("目录扫描器 V1.0") self.title("目录扫描器 V1.0")
@ -79,12 +79,13 @@ class App(tk.Tk):
self.folder_first_var = tk.BooleanVar() self.folder_first_var = tk.BooleanVar()
self.folder_first_checkbutton = tk.Checkbutton( self.folder_first_checkbutton = tk.Checkbutton(
self, text="目录优先显示", variable=self.folder_first_var self,
text="目录优先显示",
variable=self.folder_first_var,
command=self.update_folder_first_var,
) )
self.folder_first_checkbutton.pack(side=tk.LEFT, fill=tk.X) self.folder_first_checkbutton.pack(side=tk.LEFT, fill=tk.X)
self.folder_first_checkbutton.config(command=self.update_folder_first_var)
self.sort_by_size_button = tk.Button( self.sort_by_size_button = tk.Button(
self, text="根据目录/文件大小排序", command=self.sort_by_size self, text="根据目录/文件大小排序", command=self.sort_by_size
) )
@ -100,6 +101,13 @@ class App(tk.Tk):
) )
self.refresh_button.pack(side=tk.LEFT, fill=tk.X) self.refresh_button.pack(side=tk.LEFT, fill=tk.X)
self.reselect_button = tk.Button(
self, text="重新选择目录", command=self.reselect_directory
)
self.reselect_button.pack(side=tk.LEFT, fill=tk.X)
self.tree.bind("<Double-1>", self.on_double_click)
self.tree.bind("<Return>", self.on_enter_press)
self.tree.bind("<Button-3>", self.show_context_menu) self.tree.bind("<Button-3>", self.show_context_menu)
self.size_sort_order = True self.size_sort_order = True
@ -113,18 +121,47 @@ class App(tk.Tk):
self.protocol("WM_DELETE_WINDOW", self.on_close) self.protocol("WM_DELETE_WINDOW", self.on_close)
self.set_window_size() self.set_window_size()
def populate_root(self): def on_double_click(self, event): # V1.2 Update当双击的是文件时打开文件
size = self.scanner.get_size(self.start_path) / 1024 / 1024 item = self.tree.identify_row(event.y)
if item:
if os.path.isfile(item):
self.open_item(item)
def on_enter_press(self, event): # V1.2 Update当回车的是文件时打开文件
item = self.tree.selection()[0]
if os.path.isfile(item):
self.open_item(item)
def reselect_directory(self): # V1.2 Update重新选择目录
new_path = filedialog.askdirectory()
if new_path:
self.start_path = new_path
self.refresh_tree()
else:
messagebox.showinfo("提示", "没有选中任何目录,程序即将退出")
self.on_close()
def format_size(self, size): # 格式化文件/目录大小
units = ["B", "KB", "MB", "GB", "TB"]
index = 0
while size >= 1024 and index < len(units) - 1:
size /= 1024.0
index += 1
return f"{size:.2f} {units[index]}"
def populate_root(self): # 初始化根目录
size = self.scanner.get_size(self.start_path)
formatted_size = self.format_size(size)
self.tree.insert( self.tree.insert(
"", "",
"end", "end",
iid=self.start_path, iid=self.start_path,
text=f"{os.path.basename(self.start_path)} ({size:.2f} MB)", text=f"{os.path.basename(self.start_path)} ({formatted_size})",
tags=("folder",), tags=("folder",),
open=True, open=True,
) )
def update_tree(self): def update_tree(self): # 更新树状图
if not self.is_sorting: if not self.is_sorting:
while not self.queue.empty(): while not self.queue.empty():
item = self.queue.get() item = self.queue.get()
@ -134,7 +171,7 @@ class App(tk.Tk):
return return
path, size, parent, tag = item path, size, parent, tag = item
size_mb = size / 1024 / 1024 formatted_size = self.format_size(size)
parent_iid = self.get_iid(parent) parent_iid = self.get_iid(parent)
if parent == self.start_path: if parent == self.start_path:
parent_iid = self.start_path parent_iid = self.start_path
@ -143,13 +180,13 @@ class App(tk.Tk):
parent_iid, parent_iid,
"end", "end",
iid=self.get_iid(path), iid=self.get_iid(path),
text=f"{os.path.basename(path)} ({size_mb:.2f} MB)", text=f"{os.path.basename(path)} ({formatted_size})",
tags=(tag,), tags=(tag,),
open=False, open=False,
) )
self.after(100, self.update_tree) self.after(100, self.update_tree)
def update_progress(self): def update_progress(self): # 更新扫描进度
if not self.progress_window.winfo_exists(): # Check if the window still exists if not self.progress_window.winfo_exists(): # Check if the window still exists
return return
while not self.progress_queue.empty(): while not self.progress_queue.empty():
@ -165,10 +202,10 @@ class App(tk.Tk):
self.after(100, self.update_progress) self.after(100, self.update_progress)
def get_iid(self, path): def get_iid(self, path): # 获取目录/文件的 iid
return path return path
def sort_by_size(self): def sort_by_size(self): # 根据目录/文件大小排序
self.sort_items( self.sort_items(
lambda x: ( lambda x: (
0 if x[1] == "folder" and self.folder_first_var.get() else 1, 0 if x[1] == "folder" and self.folder_first_var.get() else 1,
@ -182,7 +219,7 @@ class App(tk.Tk):
) )
self.size_sort_order = not self.size_sort_order self.size_sort_order = not self.size_sort_order
def sort_by_name(self): def sort_by_name(self): # 根据目录/文件名排序
self.sort_items( self.sort_items(
lambda x: ( lambda x: (
0 if x[1] == "folder" and self.folder_first_var.get() else 1, 0 if x[1] == "folder" and self.folder_first_var.get() else 1,
@ -192,7 +229,7 @@ class App(tk.Tk):
) )
self.name_sort_order = not self.name_sort_order self.name_sort_order = not self.name_sort_order
def sort_items(self, key, reverse): def sort_items(self, key, reverse): # 排序
if self.folder_first_var.get(): if self.folder_first_var.get():
selected = self.tree.selection() selected = self.tree.selection()
if not selected: if not selected:
@ -252,14 +289,17 @@ class App(tk.Tk):
for index, item in enumerate(sorted_items): for index, item in enumerate(sorted_items):
self.tree.move(item[2], parent, index) self.tree.move(item[2], parent, index)
def show_context_menu(self, event): def show_context_menu(self, event): # 显示右键菜单
item = self.tree.identify_row(event.y) item = self.tree.identify_row(event.y)
if item: if item:
self.tree.selection_set(item) self.tree.selection_set(item)
menu = tk.Menu(self, tearoff=0) menu = tk.Menu(self, tearoff=0)
menu.add_command(label="打开文件", command=lambda: self.open_item(item))
menu.add_command( menu.add_command(
label="打开所在目录", command=lambda: self.open_directory(item) label="打开文件", command=lambda: self.open_item(item, "Open File")
)
menu.add_command(
label="打开所在目录",
command=lambda: self.open_directory(item, "Open Containing Folder"),
) )
menu.add_command( menu.add_command(
label="删除文件/目录(放入回收站)", label="删除文件/目录(放入回收站)",
@ -267,28 +307,32 @@ class App(tk.Tk):
) )
menu.post(event.x_root, event.y_root) menu.post(event.x_root, event.y_root)
def open_item(self, item): def open_item(self, item, context_menu_action=None): # 打开文件/目录
path = self.tree.item(item, "text").split(" ")[0] name = self.tree.item(item, "text").split(" ")[0]
full_path = os.path.join(self.start_path, path)
try: try:
if os.path.isdir(full_path): if os.path.isdir(item):
subprocess.Popen(f'explorer "{os.path.realpath(full_path)}"') if context_menu_action == "Open File":
subprocess.Popen(f'explorer "{os.path.realpath(item)}"')
elif context_menu_action == "Open Containing Folder":
parent_folder = os.path.dirname(item)
subprocess.Popen(f'explorer "{os.path.realpath(parent_folder)}"')
else: else:
os.startfile(full_path) os.startfile(item)
except Exception as e: except Exception as e:
messagebox.showerror("错误", f"无法打开 {full_path}\n{e}") messagebox.showerror("错误", f"无法打开 {name}\n{e}")
def open_directory(self, item): def open_directory(self, item, context_menu_action=None): # 打开所在目录
path = self.tree.item(item, "text").split(" ")[0] name = self.tree.item(item, "text").split(" ")[0]
full_path = os.path.join(self.start_path, path) if item == self.start_path:
if not os.path.isdir(full_path): parent_path = item
full_path = os.path.dirname(full_path) else:
parent_path = os.path.dirname(item)
try: try:
subprocess.Popen(f'explorer "{os.path.realpath(full_path)}"') subprocess.Popen(f'explorer "{os.path.realpath(parent_path)}"')
except Exception as e: except Exception as e:
messagebox.showerror("错误", f"无法打开 {full_path}\n{e}") messagebox.showerror("错误", f"无法打开 {name}\n{e}")
def delete_item(self, item): def delete_item(self, item): # 删除文件/目录
path = self.tree.item(item, "text").split(" ")[0] path = self.tree.item(item, "text").split(" ")[0]
full_path = os.path.normpath(os.path.join(self.start_path, path)) full_path = os.path.normpath(os.path.join(self.start_path, path))
confirm = messagebox.askyesno("提示", f"你确定要删除 {full_path} 吗?") confirm = messagebox.askyesno("提示", f"你确定要删除 {full_path} 吗?")
@ -300,24 +344,24 @@ class App(tk.Tk):
messagebox.showerror("错误", f"无法删除 {full_path}\n{e}") messagebox.showerror("错误", f"无法删除 {full_path}\n{e}")
self.refresh_tree() self.refresh_tree()
def refresh_tree(self): def refresh_tree(self): # 刷新树状图
for item in self.tree.get_children(): self.is_sorting = False # Reset sorting flag
self.tree.delete(item) self.queue.queue.clear()
self.progress_queue.queue.clear()
self.tree.delete(*self.tree.get_children())
self.populate_root() self.populate_root()
self.scanner = FolderScanner(self.start_path, self.queue, self.progress_queue) self.scanner = FolderScanner(self.start_path, self.queue, self.progress_queue)
self.progress_window = ProgressWindow(self) # Create a new progress window
self.withdraw() # Hide the main window
self.scanner.start() self.scanner.start()
self.update_tree() # Start updating the tree again self.update_tree()
self.update_progress() # Start updating the progress again self.update_progress()
def on_close(self): def on_close(self): # 关闭程序
if self.scanner.is_alive(): if self.scanner.is_alive():
self.scanner.join() self.scanner.join()
self.destroy() self.destroy()
root.destroy() root.destroy()
def set_window_size(self): def set_window_size(self): # 设置窗口大小
screen_width = self.winfo_screenwidth() screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight() screen_height = self.winfo_screenheight()
window_width = int(screen_width * 0.6) window_width = int(screen_width * 0.6)
@ -327,7 +371,7 @@ class App(tk.Tk):
) )
self.update_font_size() self.update_font_size()
def update_font_size(self): def update_font_size(self): # 更新字体大小
current_font = self.style.lookup("Treeview", "font") current_font = self.style.lookup("Treeview", "font")
font_size = int(current_font.split()[1]) font_size = int(current_font.split()[1])
screen_width = self.winfo_screenwidth() screen_width = self.winfo_screenwidth()
@ -340,12 +384,12 @@ class App(tk.Tk):
font=(current_font.split()[0], new_font_size + 2, "bold"), font=(current_font.split()[0], new_font_size + 2, "bold"),
) )
def update_folder_first_var(self): def update_folder_first_var(self): # 更新目录优先显示变量
self.folder_first_var.set(not self.folder_first_var.get()) self.folder_first_var.set(not self.folder_first_var.get())
class ProgressWindow(tk.Toplevel): class ProgressWindow(tk.Toplevel): # 进度窗口
def __init__(self, parent): def __init__(self, parent): # 初始化
super().__init__(parent) super().__init__(parent)
self.title("扫描进度") self.title("扫描进度")
self.geometry("500x200") self.geometry("500x200")
@ -365,7 +409,7 @@ class ProgressWindow(tk.Toplevel):
self.protocol("WM_DELETE_WINDOW", self.on_close) self.protocol("WM_DELETE_WINDOW", self.on_close)
def on_close(self): def on_close(self):
pass # Prevent the user from closing the progress window pass # 禁止关闭进度窗口
if __name__ == "__main__": if __name__ == "__main__":