Python实例题
题目
要求:
解题思路:
代码实现:
基于 Python 的简单文件管理器
tkinter
构建图形用户界面。tkinter
构建多窗口界面。os
和 shutil
模块进行文件操作。import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
import shutil
import time
import threading
import zipfile
from datetime import datetime
class FileManager:
def __init__(self, root):
self.root = root
self.root.title("文件管理器")
self.root.geometry("900x600")
# 当前路径
self.current_path = os.getcwd()
# 创建主界面
self.create_main_window()
def create_main_window(self):
"""创建主窗口"""
# 创建顶部导航栏
nav_frame = ttk.Frame(self.root)
nav_frame.pack(fill=tk.X, padx=10, pady=10)
# 返回上级目录按钮
ttk.Button(nav_frame, text="返回上级", command=self.go_up).pack(side=tk.LEFT, padx=5)
# 当前路径显示
self.path_var = tk.StringVar(value=self.current_path)
path_entry = ttk.Entry(nav_frame, textvariable=self.path_var, width=100)
path_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
path_entry.bind("", lambda event: self.navigate_to_path())
# 刷新按钮
ttk.Button(nav_frame, text="刷新", command=self.refresh).pack(side=tk.LEFT, padx=5)
# 创建工具栏
toolbar = ttk.Frame(self.root)
toolbar.pack(fill=tk.X, padx=10, pady=5)
# 文件操作按钮
ttk.Button(toolbar, text="新建文件夹", command=self.create_folder).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="新建文件", command=self.create_file).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="删除", command=self.delete_items).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="重命名", command=self.rename_item).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="复制", command=self.copy_items).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="移动", command=self.move_items).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="压缩", command=self.zip_items).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="解压", command=self.unzip_item).pack(side=tk.LEFT, padx=5)
# 创建搜索框
search_frame = ttk.Frame(self.root)
search_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(search_frame, text="搜索:").pack(side=tk.LEFT, padx=5)
self.search_var = tk.StringVar()
search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=50)
search_entry.pack(side=tk.LEFT, padx=5)
ttk.Button(search_frame, text="搜索", command=self.search_files).pack(side=tk.LEFT, padx=5)
ttk.Button(search_frame, text="清除搜索", command=self.clear_search).pack(side=tk.LEFT, padx=5)
# 创建主框架
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 创建文件列表
columns = ("name", "type", "size", "modified")
self.file_tree = ttk.Treeview(main_frame, columns=columns, show="headings")
self.file_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 设置列标题
self.file_tree.heading("name", text="名称")
self.file_tree.heading("type", text="类型")
self.file_tree.heading("size", text="大小")
self.file_tree.heading("modified", text="修改日期")
# 设置列宽
self.file_tree.column("name", width=300)
self.file_tree.column("type", width=100)
self.file_tree.column("size", width=100, anchor=tk.E)
self.file_tree.column("modified", width=180)
# 绑定双击事件
self.file_tree.bind("", self.on_double_click)
# 创建滚动条
scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.file_tree.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.file_tree.configure(yscroll=scrollbar.set)
# 创建状态栏
self.status_var = tk.StringVar(value=f"就绪")
ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(side=tk.BOTTOM, fill=tk.X)
# 加载文件列表
self.load_files()
def load_files(self, path=None):
"""加载文件列表"""
if path:
self.current_path = path
self.path_var.set(path)
# 清空现有数据
for item in self.file_tree.get_children():
self.file_tree.delete(item)
try:
# 获取文件和文件夹列表
items = os.listdir(self.current_path)
# 先添加文件夹,再添加文件(按名称排序)
folders = []
files = []
for item in items:
item_path = os.path.join(self.current_path, item)
if os.path.isdir(item_path):
folders.append(item)
else:
files.append(item)
# 排序
folders.sort(key=str.lower)
files.sort(key=str.lower)
# 添加到列表
for item in folders + files:
item_path = os.path.join(self.current_path, item)
try:
# 获取文件信息
stats = os.stat(item_path)
modified_time = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
if os.path.isdir(item_path):
# 文件夹
item_type = "文件夹"
item_size = ""
self.file_tree.insert("", tk.END, values=(item, item_type, item_size, modified_time), tags=('folder',))
else:
# 文件
item_type = self.get_file_type(item)
item_size = self.format_size(stats.st_size)
self.file_tree.insert("", tk.END, values=(item, item_type, item_size, modified_time))
except Exception as e:
# 无法访问的文件/文件夹
self.file_tree.insert("", tk.END, values=(item, "无法访问", "", ""), tags=('error',))
# 设置样式
self.file_tree.tag_configure('folder', foreground='blue')
self.file_tree.tag_configure('error', foreground='red')
self.status_var.set(f"已加载 {len(items)} 个项目")
except Exception as e:
messagebox.showerror("错误", f"无法加载目录: {str(e)}")
self.status_var.set(f"加载失败: {str(e)}")
def get_file_type(self, filename):
"""获取文件类型"""
ext = os.path.splitext(filename)[1].lower()
# 常见文件类型
if ext == '.txt':
return "文本文件"
elif ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']:
return "图像文件"
elif ext in ['.mp3', '.wav', '.ogg', '.flac']:
return "音频文件"
elif ext in ['.mp4', '.avi', '.mkv', '.mov']:
return "视频文件"
elif ext in ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']:
return "文档文件"
elif ext in ['.py', '.java', '.c', '.cpp', '.html', '.css', '.js']:
return "代码文件"
elif ext == '.zip':
return "压缩文件"
else:
return "文件"
def format_size(self, size_bytes):
"""格式化文件大小"""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes/1024:.1f} KB"
elif size_bytes < 1024 * 1024 * 1024:
return f"{size_bytes/(1024*1024):.1f} MB"
else:
return f"{size_bytes/(1024*1024*1024):.1f} GB"
def go_up(self):
"""返回上级目录"""
parent_path = os.path.dirname(self.current_path)
if parent_path != self.current_path: # 防止在根目录时出错
self.load_files(parent_path)
def navigate_to_path(self):
"""导航到输入的路径"""
new_path = self.path_var.get()
if os.path.exists(new_path) and os.path.isdir(new_path):
self.load_files(new_path)
else:
messagebox.showerror("错误", "无效的路径")
self.path_var.set(self.current_path)
def refresh(self):
"""刷新当前目录"""
self.load_files(self.current_path)
def on_double_click(self, event):
"""双击事件处理"""
item = self.file_tree.identify_row(event.y)
if item:
item_name = self.file_tree.item(item, "values")[0]
item_path = os.path.join(self.current_path, item_name)
if os.path.isdir(item_path):
# 打开文件夹
self.load_files(item_path)
else:
# 尝试预览文件
self.preview_file(item_path)
def preview_file(self, file_path):
"""预览文件"""
try:
# 获取文件扩展名
ext = os.path.splitext(file_path)[1].lower()
# 只预览文本文件
if ext in ['.txt', '.py', '.java', '.c', '.cpp', '.html', '.css', '.js', '.json', '.xml', '.md']:
# 创建预览窗口
preview_window = tk.Toplevel(self.root)
preview_window.title(f"预览: {os.path.basename(file_path)}")
preview_window.geometry("800x600")
# 创建文本区域
text_area = scrolledtext.ScrolledText(preview_window, wrap=tk.WORD)
text_area.pack(fill=tk.BOTH, expand=True)
# 读取文件内容
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
text_area.insert(tk.END, content)
except UnicodeDecodeError:
# 尝试其他编码
try:
with open(file_path, 'r', encoding='gbk') as file:
content = file.read()
text_area.insert(tk.END, content)
except Exception as e:
text_area.insert(tk.END, f"无法解码文件内容: {str(e)}")
# 设置为只读
text_area.config(state=tk.DISABLED)
else:
messagebox.showinfo("提示", f"无法预览 {ext} 类型的文件")
except Exception as e:
messagebox.showerror("错误", f"预览文件失败: {str(e)}")
def create_folder(self):
"""创建文件夹"""
folder_name = simpledialog.askstring("新建文件夹", "请输入文件夹名称:", parent=self.root)
if folder_name:
folder_path = os.path.join(self.current_path, folder_name)
try:
if not os.path.exists(folder_path):
os.makedirs(folder_path)
self.refresh()
self.status_var.set(f"已创建文件夹: {folder_name}")
else:
messagebox.showerror("错误", "文件夹已存在")
except Exception as e:
messagebox.showerror("错误", f"创建文件夹失败: {str(e)}")
def create_file(self):
"""创建文件"""
file_name = simpledialog.askstring("新建文件", "请输入文件名称:", parent=self.root)
if file_name:
file_path = os.path.join(self.current_path, file_name)
try:
if not os.path.exists(file_path):
# 创建空文件
with open(file_path, 'w') as f:
pass
self.refresh()
self.status_var.set(f"已创建文件: {file_name}")
else:
messagebox.showerror("错误", "文件已存在")
except Exception as e:
messagebox.showerror("错误", f"创建文件失败: {str(e)}")
def delete_items(self):
"""删除选中的项目"""
selected_items = self.file_tree.selection()
if not selected_items:
messagebox.showinfo("提示", "请选择要删除的项目")
return
# 获取选中项目的名称和类型
items_to_delete = []
for item in selected_items:
item_name = self.file_tree.item(item, "values")[0]
item_path = os.path.join(self.current_path, item_name)
items_to_delete.append((item_name, item_path))
# 确认删除
if messagebox.askyesno("确认删除", f"确定要删除以下 {len(items_to_delete)} 个项目吗?\n\n" +
"\n".join([name for name, _ in items_to_delete])):
try:
# 在单独的线程中执行删除操作
delete_thread = threading.Thread(target=self._delete_items_thread, args=(items_to_delete,))
delete_thread.start()
# 显示进度窗口
self.show_progress_window("正在删除...", delete_thread)
except Exception as e:
messagebox.showerror("错误", f"删除项目失败: {str(e)}")
def _delete_items_thread(self, items_to_delete):
"""在单独的线程中执行删除操作"""
for name, path in items_to_delete:
try:
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(path)
except Exception as e:
# 如果删除失败,继续删除其他项目
self.status_var.set(f"删除 {name} 失败: {str(e)}")
# 刷新文件列表
self.root.after(100, self.refresh)
def rename_item(self):
"""重命名选中的项目"""
selected_items = self.file_tree.selection()
if not selected_items or len(selected_items) > 1:
messagebox.showinfo("提示", "请选择一个项目进行重命名")
return
item = selected_items[0]
old_name = self.file_tree.item(item, "values")[0]
old_path = os.path.join(self.current_path, old_name)
new_name = simpledialog.askstring("重命名", "请输入新名称:", initialvalue=old_name, parent=self.root)
if new_name and new_name != old_name:
new_path = os.path.join(self.current_path, new_name)
try:
if not os.path.exists(new_path):
os.rename(old_path, new_path)
self.refresh()
self.status_var.set(f"已重命名: {old_name} -> {new_name}")
else:
messagebox.showerror("错误", "目标已存在")
except Exception as e:
messagebox.showerror("错误", f"重命名失败: {str(e)}")
def copy_items(self):
"""复制选中的项目"""
selected_items = self.file_tree.selection()
if not selected_items:
messagebox.showinfo("提示", "请选择要复制的项目")
return
# 获取选中项目的路径
self.items_to_copy = []
for item in selected_items:
item_name = self.file_tree.item(item, "values")[0]
item_path = os.path.join(self.current_path, item_name)
self.items_to_copy.append((item_name, item_path))
# 打开目标目录选择对话框
target_dir = filedialog.askdirectory(title="选择目标目录", initialdir=self.current_path)
if target_dir:
try:
# 在单独的线程中执行复制操作
copy_thread = threading.Thread(target=self._copy_items_thread, args=(target_dir,))
copy_thread.start()
# 显示进度窗口
self.show_progress_window("正在复制...", copy_thread)
except Exception as e:
messagebox.showerror("错误", f"复制项目失败: {str(e)}")
def _copy_items_thread(self, target_dir):
"""在单独的线程中执行复制操作"""
for name, source_path in self.items_to_copy:
target_path = os.path.join(target_dir, name)
try:
if os.path.isdir(source_path):
shutil.copytree(source_path, target_path)
else:
shutil.copy2(source_path, target_path)
except Exception as e:
# 如果复制失败,继续复制其他项目
self.status_var.set(f"复制 {name} 失败: {str(e)}")
# 刷新目标目录(如果在当前窗口可见)
if target_dir == self.current_path:
self.root.after(100, self.refresh)
def move_items(self):
"""移动选中的项目"""
selected_items = self.file_tree.selection()
if not selected_items:
messagebox.showinfo("提示", "请选择要移动的项目")
return
# 获取选中项目的路径
self.items_to_move = []
for item in selected_items:
item_name = self.file_tree.item(item, "values")[0]
item_path = os.path.join(self.current_path, item_name)
self.items_to_move.append((item_name, item_path))
# 打开目标目录选择对话框
target_dir = filedialog.askdirectory(title="选择目标目录", initialdir=self.current_path)
if target_dir:
try:
# 在单独的线程中执行移动操作
move_thread = threading.Thread(target=self._move_items_thread, args=(target_dir,))
move_thread.start()
# 显示进度窗口
self.show_progress_window("正在移动...", move_thread)
except Exception as e:
messagebox.showerror("错误", f"移动项目失败: {str(e)}")
def _move_items_thread(self, target_dir):
"""在单独的线程中执行移动操作"""
for name, source_path in self.items_to_move:
target_path = os.path.join(target_dir, name)
try:
shutil.move(source_path, target_path)
except Exception as e:
# 如果移动失败,继续移动其他项目
self.status_var.set(f"移动 {name} 失败: {str(e)}")
# 刷新当前目录和目标目录(如果在当前窗口可见)
self.root.after(100, self.refresh)
if target_dir == self.current_path:
self.root.after(100, lambda: self.load_files(target_dir))
def zip_items(self):
"""压缩选中的项目"""
selected_items = self.file_tree.selection()
if not selected_items:
messagebox.showinfo("提示", "请选择要压缩的项目")
return
# 获取选中项目的路径
items_to_zip = []
for item in selected_items:
item_name = self.file_tree.item(item, "values")[0]
item_path = os.path.join(self.current_path, item_name)
items_to_zip.append((item_name, item_path))
# 确定压缩文件名
default_zip_name = "archive.zip"
if len(items_to_zip) == 1:
base_name = os.path.splitext(items_to_zip[0][0])[0]
default_zip_name = f"{base_name}.zip"
# 打开保存对话框
zip_file_path = filedialog.asksaveasfilename(
title="保存压缩文件",
defaultextension=".zip",
filetypes=[("ZIP 文件", "*.zip"), ("所有文件", "*.*")],
initialdir=self.current_path,
initialfile=default_zip_name
)
if zip_file_path:
try:
# 在单独的线程中执行压缩操作
zip_thread = threading.Thread(target=self._zip_items_thread, args=(items_to_zip, zip_file_path))
zip_thread.start()
# 显示进度窗口
self.show_progress_window("正在压缩...", zip_thread)
except Exception as e:
messagebox.showerror("错误", f"压缩项目失败: {str(e)}")
def _zip_items_thread(self, items_to_zip, zip_file_path):
"""在单独的线程中执行压缩操作"""
try:
with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for name, path in items_to_zip:
if os.path.isdir(path):
# 压缩文件夹
for root, dirs, files in os.walk(path):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, self.current_path)
zipf.write(file_path, arcname)
else:
# 压缩文件
arcname = os.path.relpath(path, self.current_path)
zipf.write(path, arcname)
self.status_var.set(f"已压缩到: {os.path.basename(zip_file_path)}")
# 刷新当前目录
self.root.after(100, self.refresh)
except Exception as e:
self.status_var.set(f"压缩失败: {str(e)}")
def unzip_item(self):
"""解压选中的项目"""
selected_items = self.file_tree.selection()
if not selected_items or len(selected_items) > 1:
messagebox.showinfo("提示", "请选择一个压缩文件进行解压")
return
item = selected_items[0]
item_name = self.file_tree.item(item, "values")[0]
item_path = os.path.join(self.current_path, item_name)
# 检查是否为ZIP文件
if not item_name.lower().endswith('.zip'):
messagebox.showinfo("提示", "请选择一个ZIP压缩文件")
return
# 打开目标目录选择对话框
target_dir = filedialog.askdirectory(title="选择解压目录", initialdir=self.current_path)
if target_dir:
try:
# 在单独的线程中执行解压操作
unzip_thread = threading.Thread(target=self._unzip_item_thread, args=(item_path, target_dir))
unzip_thread.start()
# 显示进度窗口
self.show_progress_window("正在解压...", unzip_thread)
except Exception as e:
messagebox.showerror("错误", f"解压文件失败: {str(e)}")
def _unzip_item_thread(self, zip_file_path, target_dir):
"""在单独的线程中执行解压操作"""
try:
with zipfile.ZipFile(zip_file_path, 'r') as zipf:
zipf.extractall(target_dir)
self.status_var.set(f"已解压到: {target_dir}")
# 刷新目标目录(如果在当前窗口可见)
if target_dir == self.current_path:
self.root.after(100, self.refresh)
except Exception as e:
self.status_var.set(f"解压失败: {str(e)}")
def show_progress_window(self, message, thread):
"""显示进度窗口"""
progress_window = tk.Toplevel(self.root)
progress_window.title("请等待")
progress_window.geometry("300x120")
progress_window.transient(self.root)
progress_window.grab_set()
ttk.Label(progress_window, text=message, padding=20).pack()
progress_bar = ttk.Progressbar(progress_window, mode='indeterminate')
progress_bar.pack(fill=tk.X, padx=20)
progress_bar.start()
def check_thread():
if thread.is_alive():
progress_window.after(100, check_thread)
else:
progress_window.destroy()
progress_window.after(100, check_thread)
def search_files(self):
"""搜索文件"""
search_text = self.search_var.get().strip()
if not search_text:
messagebox.showinfo("提示", "请输入搜索关键词")
return
try:
# 在单独的线程中执行搜索操作
search_thread = threading.Thread(target=self._search_files_thread, args=(search_text,))
search_thread.start()
# 显示进度窗口
self.show_progress_window("正在搜索...", search_thread)
except Exception as e:
messagebox.showerror("错误", f"搜索失败: {str(e)}")
def _search_files_thread(self, search_text):
"""在单独的线程中执行搜索操作"""
# 清空现有数据
self.root.after(100, lambda: [self.file_tree.delete(item) for item in self.file_tree.get_children()])
results = []
try:
# 递归搜索当前目录
for root, dirs, files in os.walk(self.current_path):
# 搜索文件夹
for dir_name in dirs:
if search_text.lower() in dir_name.lower():
dir_path = os.path.join(root, dir_name)
try:
stats = os.stat(dir_path)
modified_time = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
results.append((dir_name, "文件夹", "", modified_time, dir_path))
except:
results.append((dir_name, "文件夹", "", "", dir_path))
# 搜索文件
for file_name in files:
if search_text.lower() in file_name.lower():
file_path = os.path.join(root, file_name)
try:
stats = os.stat(file_path)
modified_time = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
file_size = self.format_size(stats.st_size)
results.append((file_name, self.get_file_type(file_name), file_size, modified_time, file_path))
except:
results.append((file_name, "文件", "", "", file_path))
# 在主线程中更新UI
self.root.after(100, self._update_search_results, results)
except Exception as e:
self.root.after(100, lambda: messagebox.showerror("错误", f"搜索过程中出错: {str(e)}"))
def _update_search_results(self, results):
"""更新搜索结果"""
# 清空现有数据
for item in self.file_tree.get_children():
self.file_tree.delete(item)
# 添加搜索结果
for name, item_type, size, modified, path in results:
if item_type == "文件夹":
self.file_tree.insert("", tk.END, values=(name, item_type, size, modified), tags=('folder',))
else:
self.file_tree.insert("", tk.END, values=(name, item_type, size, modified))
# 设置样式
self.file_tree.tag_configure('folder', foreground='blue')
self.status_var.set(f"找到 {len(results)} 个匹配项")
def clear_search(self):
"""清除搜索结果,显示原始目录内容"""
self.search_var.set("")
self.load_files()
if __name__ == "__main__":
root = tk.Tk()
app = FileManager(root)
root.mainloop()