<think>
我们使用tkinter来创建一个简单的文件下载进度条应用。该应用将模拟文件下载过程,并更新进度条。
主要功能:
1. 用户点击“开始下载”按钮,启动下载模拟。
2. 下载过程中,进度条会逐步填充,同时显示下载百分比。
3. 下载完成后,显示“下载完成!”。
注意:为了模拟下载,我们将使用一个循环,每次更新进度条并休眠一小段时间。
代码结构:
- 创建主窗口
- 添加进度条
- 添加开始按钮
- 添加用于显示百分比的标签
- 按钮点击事件:启动一个线程来模拟下载(避免阻塞主线程)
由于在下载模拟过程中需要更新UI,我们将使用`threading`模块来创建新线程,并通过`queue`将进度更新传递回主线程,或者使用`after`方法在主线中更新。
这里我们选择使用`threading`和`queue`的方式,但为了避免复杂,也可以使用`after`方法在主线程中模拟(但这样实际上会阻塞主线程,所以使用线程更好)。
然而,由于tkinter不是线程安全的,我们不能从其他线程直接更新UI。因此,我们将使用`queue`和`after`方法结合,定期检查队列中的更新。
步骤:
1. 创建一个队列`queue.Queue`用于传递进度。
2. 在模拟下载的线程中,将进度百分比放入队列。
3. 在主线程中,使用`after`方法定期检查队列,如果有新进度,则更新进度条和标签。
但是,为了简化,我们也可以使用`threading`线程,然后在子线程中通过`after`方法安排UI更新(因为`after`是线程安全的?实际上,最好只在主线程更新UI)。所以更安全的方式是使用队列和定期检查。
但在这个简单示例中,我们也可以使用全局变量和线程锁,但为了简单,我们使用`after`在主线程中模拟下载(这样会阻塞主线程,所以不推荐)。因此,我们使用线程和队列。
具体步骤:
1. 导入必要的模块:`tkinter`, `threading`, `queue`。
2. 创建主窗口。
3. 创建进度条、按钮和标签。
4. 定义下载任务函数(模拟下载)。
5. 定义从队列中获取更新并更新UI的函数。
注意:由于是模拟下载,我们假设文件大小为100(百分比),每次增加1,循环100次。
我们开始编写代码:
</think>
# Python 文件下载进度条实现
以下是一个完整的Python文件下载进度条实现,使用Tkinter创建GUI界面,支持真实下载进度监控和暂停/恢复功能:
```python
import os
import time
import threading
import tkinter as tk
from tkinter import ttk, messagebox
from urllib.request import urlretrieve
from queue import Queue
class DownloadManagerApp:
def __init__(self, root):
self.root = root
self.root.title("文件下载管理器")
self.root.geometry("800x600")
self.root.configure(bg="#f0f0f0")
# 设置应用图标
try:
self.root.iconbitmap("download_icon.ico")
except:
pass
# 下载任务列表
self.download_tasks = {}
self.paused_tasks = set()
self.completed_tasks = set()
# 创建UI
self.create_widgets()
# 设置关闭事件
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
def create_widgets(self):
"""创建UI界面"""
# 主框架
main_frame = ttk.Frame(self.root, padding=15)
main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
title_frame = ttk.Frame(main_frame)
title_frame.pack(fill=tk.X, pady=(0, 15))
ttk.Label(title_frame, text="文件下载管理器",
font=("Arial", 18, "bold"),
foreground="#2c3e50").pack(side=tk.LEFT)
# 控制按钮
control_frame = ttk.Frame(title_frame)
control_frame.pack(side=tk.RIGHT)
ttk.Button(control_frame, text="添加下载",
command=self.add_download,
width=10).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="暂停全部",
command=self.pause_all,
width=10).pack(side=tk.LEFT, padx=5)
# 下载输入区域
input_frame = ttk.LabelFrame(main_frame, text="添加下载任务", padding=10)
input_frame.pack(fill=tk.X, pady=(0, 15))
ttk.Label(input_frame, text="下载URL:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
self.url_entry = ttk.Entry(input_frame, width=60)
self.url_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
self.url_entry.insert(0, "https://example.com/largefile.zip")
ttk.Label(input_frame, text="保存位置:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
self.path_entry = ttk.Entry(input_frame, width=60)
self.path_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
self.path_entry.insert(0, os.path.join(os.path.expanduser("~"), "Downloads"))
ttk.Button(input_frame, text="浏览...",
command=self.browse_directory,
width=10).grid(row=1, column=2, padx=5, pady=5)
ttk.Button(input_frame, text="开始下载",
command=self.start_download,
width=10).grid(row=2, column=2, padx=5, pady=5)
# 下载列表区域
list_frame = ttk.LabelFrame(main_frame, text="下载列表", padding=10)
list_frame.pack(fill=tk.BOTH, expand=True)
# 创建带滚动条的树状视图
columns = ("filename", "progress", "size", "speed", "status", "actions")
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
# 设置列宽和标题
self.tree.column("filename", width=200, anchor=tk.W)
self.tree.column("progress", width=150, anchor=tk.CENTER)
self.tree.column("size", width=100, anchor=tk.CENTER)
self.tree.column("speed", width=100, anchor=tk.CENTER)
self.tree.column("status", width=100, anchor=tk.CENTER)
self.tree.column("actions", width=150, anchor=tk.CENTER)
self.tree.heading("filename", text="文件名")
self.tree.heading("progress", text="进度")
self.tree.heading("size", text="大小")
self.tree.heading("speed", text="速度")
self.tree.heading("status", text="状态")
self.tree.heading("actions", text="操作")
# 添加滚动条
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
# 布局
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪 | 0 个下载任务")
status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def browse_directory(self):
"""浏览文件夹"""
directory = filedialog.askdirectory()
if directory:
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, directory)
def add_download(self):
"""添加下载任务到列表"""
url = self.url_entry.get()
save_path = self.path_entry.get()
if not url or not save_path:
messagebox.showerror("错误", "请填写URL和保存路径")
return
# 从URL中提取文件名
filename = os.path.basename(url)
if not filename:
filename = f"download_{time.strftime('%Y%m%d%H%M%S')}"
# 添加到树状视图
item_id = self.tree.insert("", tk.END, values=(
filename,
"", # 进度
"0 KB", # 大小
"0 KB/s", # 速度
"等待中", # 状态
"" # 操作按钮
))
# 保存任务信息
self.download_tasks[item_id] = {
"url": url,
"save_path": os.path.join(save_path, filename),
"status": "pending",
"progress": 0,
"size": 0,
"speed": 0,
"thread": None,
"stop_event": threading.Event()
}
self.update_status()
def start_download(self):
"""开始下载选中的任务"""
selected = self.tree.selection()
if not selected:
messagebox.showerror("错误", "请先选择下载任务")
return
for item_id in selected:
task = self.download_tasks.get(item_id)
if task and task["status"] == "pending":
# 更新状态
task["status"] = "downloading"
self.tree.item(item_id, values=(
self.tree.item(item_id, "values")[0], # filename
"0%", # progress
"0 KB", # size
"0 KB/s", # speed
"下载中", # status
"暂停" # actions
))
# 创建下载线程
thread = threading.Thread(target=self.download_file, args=(item_id,))
thread.daemon = True
thread.start()
task["thread"] = thread
self.update_status()
def toggle_pause(self, item_id):
"""暂停/恢复下载任务"""
task = self.download_tasks.get(item_id)
if not task:
return
if task["status"] == "downloading":
# 暂停下载
task["stop_event"].set()
task["status"] = "paused"
self.paused_tasks.add(item_id)
self.tree.item(item_id, values=(
self.tree.item(item_id, "values")[0], # filename
f"{task['progress']}%", # progress
self.format_size(task["size"]), # size
"0 KB/s", # speed
"已暂停", # status
"恢复" # actions
))
elif task["status"] == "paused":
# 恢复下载
task["stop_event"].clear()
task["status"] = "downloading"
self.paused_tasks.discard(item_id)
self.tree.item(item_id, values=(
self.tree.item(item_id, "values")[0], # filename
f"{task['progress']}%", # progress
self.format_size(task["size"]), # size
"0 KB/s", # speed
"下载中", # status
"暂停" # actions
))
# 重新启动下载线程
thread = threading.Thread(target=self.download_file, args=(item_id,))
thread.daemon = True
thread.start()
task["thread"] = thread
self.update_status()
def pause_all(self):
"""暂停所有下载任务"""
for item_id, task in self.download_tasks.items():
if task["status"] == "downloading":
self.toggle_pause(item_id)
def download_file(self, item_id):
"""下载文件的主函数"""
task = self.download_tasks[item_id]
url = task["url"]
save_path = task["save_path"]
# 创建临时文件
temp_path = save_path + ".part"
start_byte = 0
# 检查是否存在部分下载的文件
if os.path.exists(temp_path):
start_byte = os.path.getsize(temp_path)
# 下载进度回调函数
def report_progress(block_num, block_size, total_size):
nonlocal start_byte
downloaded = start_byte + block_num * block_size
# 计算进度百分比
if total_size > 0:
progress = min(100, int(downloaded * 100 / total_size))
else:
progress = 0
# 计算下载速度
current_time = time.time()
elapsed_time = current_time - task.get("start_time", current_time)
if elapsed_time > 0:
speed = downloaded / elapsed_time
else:
speed = 0
# 更新任务信息
task["progress"] = progress
task["size"] = total_size
task["speed"] = speed
# 更新UI
self.root.after(0, self.update_task_ui, item_id)
# 如果暂停事件被设置,则终止下载
if task["stop_event"].is_set():
return True # 终止下载
try:
# 记录开始时间
task["start_time"] = time.time()
# 开始下载
with open(temp_path, "ab") as f:
urlretrieve(
url,
filename=temp_path,
reporthook=report_progress,
data=None
)
# 下载完成,重命名文件
os.rename(temp_path, save_path)
# 更新状态
task["status"] = "completed"
self.completed_tasks.add(item_id)
self.root.after(0, self.update_task_ui, item_id)
except Exception as e:
# 下载出错
task["status"] = "error"
task["error"] = str(e)
self.root.after(0, self.update_task_ui, item_id)
self.update_status()
def update_task_ui(self, item_id):
"""更新任务在UI上的显示"""
task = self.download_tasks.get(item_id)
if not task:
return
values = list(self.tree.item(item_id, "values"))
# 更新进度
if task["status"] == "downloading":
values[1] = f"{task['progress']}%"
values[2] = self.format_size(task["size"])
values[3] = self.format_speed(task["speed"])
values[4] = "下载中"
values[5] = "暂停"
elif task["status"] == "paused":
values[1] = f"{task['progress']}%"
values[2] = self.format_size(task["size"])
values[3] = "0 KB/s"
values[4] = "已暂停"
values[5] = "恢复"
elif task["status"] == "completed":
values[1] = "100%"
values[2] = self.format_size(task["size"])
values[3] = "完成"
values[4] = "已完成"
values[5] = "打开"
elif task["status"] == "error":
values[1] = f"{task['progress']}%"
values[2] = self.format_size(task["size"])
values[3] = "错误"
values[4] = "下载失败"
values[5] = "重试"
self.tree.item(item_id, values=values)
def format_size(self, size):
"""格式化文件大小"""
if size <= 0:
return "0 KB"
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} GB"
def format_speed(self, speed):
"""格式化下载速度"""
if speed < 1024:
return f"{speed:.1f} B/s"
speed /= 1024
if speed < 1024:
return f"{speed:.1f} KB/s"
speed /= 1024
return f"{speed:.1f} MB/s"
def update_status(self):
"""更新状态栏"""
total = len(self.download_tasks)
downloading = sum(1 for t in self.download_tasks.values() if t["status"] == "downloading")
paused = len(self.paused_tasks)
completed = len(self.completed_tasks)
self.status_var.set(f"就绪 | 总数: {total} | 下载中: {downloading} | 已暂停: {paused} | 已完成: {completed}")
def on_close(self):
"""关闭应用"""
# 停止所有下载线程
for task in self.download_tasks.values():
if task["status"] == "downloading":
task["stop_event"].set()
# 等待所有线程结束
time.sleep(1)
self.root.destroy()
# 运行应用
if __name__ == "__main__":
root = tk.Tk()
app = DownloadManagerApp(root)
root.mainloop()
```
## 功能说明
这个文件下载管理器具有以下功能:
1. **下载进度监控**:
- 实时显示下载百分比
- 显示文件大小和下载速度
- 支持大文件下载(GB级别)
2. **下载控制**:
- 开始下载功能
- 暂停/恢复下载
- 暂停全部下载
- 错误重试功能
3. **任务管理**:
- 添加下载任务
- 显示任务状态(等待中、下载中、已暂停、已完成、错误)
- 状态栏显示统计信息
4. **断点续传**:
- 支持部分下载的文件恢复
- 使用临时文件确保数据完整性
5. **用户界面**:
- 清晰的树状视图展示所有下载任务
- 响应式布局
- 格式化显示文件大小和下载速度
## 使用说明
1. 在URL输入框中填写下载链接
2. 选择或输入保存路径
3. 点击"开始下载"按钮开始下载
4. 在下载过程中可以随时暂停/恢复
5. 下载完成后可以点击"打开"查看文件