如何为pyAesCrypt添加进度条并解决tkinter加密时窗口无响应问题
实现方案
以下修改可同时解决窗口阻塞和进度条显示问题:
- 引入
threading模块,将加密/解密的计算逻辑放到独立子线程执行,不会阻塞tk主UI线程的渲染和响应 - 界面新增进度条组件,处理过程中实时更新百分比
- 调整原有的流处理逻辑,手动按缓冲区大小分块迭代处理,每处理完一块就统计已处理字节数,计算进度后更新到UI
完整修改代码
import pyAesCrypt import tkinter as tk from tkinter import ttk import easygui import os from os import stat, remove from cryptography.fernet import Fernet import tkinter.messagebox from tkinter import * import sys import threading password = "" window = tk.Tk() window.title("Encrypt V4.1") window.geometry("854x520") expvar = tkinter.IntVar() exp = Checkbutton(text="完成后在资源管理器中显示文件", variable=expvar) exp.pack() # 新增进度条组件 progress_var = tk.DoubleVar() progress_bar = ttk.Progressbar(window, variable=progress_var, maximum=100) progress_bar.pack(pady=10, fill=X, padx=20) progress_label = tk.Label(window, text="等待操作...") progress_label.pack() bufferSize = 128 * 1024 # 进度更新函数,只能在主线程调用 def update_progress(processed, total): percent = min(100, (processed / total) * 100) progress_var.set(percent) progress_label.config(text=f"处理进度:{percent:.1f}%") window.update_idletasks() def warn_nokey(): easygui.msgbox("未加载密钥,请先加载密钥", ok_button="加载/生成密钥") initkey = easygui.choicebox("请选择生成新密钥还是加载现有密钥?", choices=['加载', '生成', '退出']) if initkey == "加载": pwd() elif initkey == "生成": keygen() else: sys.exit(0) def pwd(): global password try: password = open(easygui.fileopenbox(filetypes=["*.key"], default="*.key")).read() except TypeError: pass def keygen(): global password try: key = Fernet.generate_key() save_path = easygui.filesavebox(filetypes=["key.key"], default="key.key") with open(save_path, 'wb') as filekey: filekey.write(key) password = key.decode("utf-8") except TypeError: pass # 加密逻辑,放到子线程运行 def encrypt_thread(file): try: total_size = stat(file).st_size name = os.path.splitext(file)[0] processed = 0 # 初始化加密上下文 ctx = pyAesCrypt.EncryptionContext(password, bufferSize) with open(file, "rb") as fIn: with open(str(name) + ".tmp", "wb") as fOut: while True: chunk = fIn.read(bufferSize) if not chunk: # 处理最后一块 fOut.write(ctx.finalize()) break # 加密块并写入 fOut.write(ctx.update(chunk)) processed += len(chunk) # 调度到主线程更新进度 window.after(0, update_progress, processed, total_size) remove(file) os.rename(name + ".tmp", file) # 重置进度 window.after(0, update_progress, 0, 1) window.after(0, lambda: progress_label.config(text="加密完成")) if expvar.get() == 1: os.system("explorer /select," + str(file)) else: tkinter.messagebox.showinfo("加密", "完成!文件已保存到 " + file) except Exception as e: window.after(0, lambda: progress_label.config(text="加密失败")) tkinter.messagebox.showerror("错误", f"加密失败:{str(e)}") def encrypt(): if password == "" or password is None: warn_nokey() else: try: file = easygui.fileopenbox() if not file: return progress_label.config(text="正在加密...") # 启动子线程执行加密 threading.Thread(target=encrypt_thread, args=(file,), daemon=True).start() except TypeError: pass # 解密逻辑,放到子线程运行 def decrypt_thread(file): try: enc_file_size = stat(file).st_size name = os.path.splitext(file)[0] processed = 0 # 初始化解密上下文 ctx = pyAesCrypt.DecryptionContext(password, bufferSize, enc_file_size) with open(file, "rb") as fIn: with open(str(name) + ".tmp", "wb") as fOut: while True: chunk = fIn.read(bufferSize) if not chunk: # 处理最后一块 fOut.write(ctx.finalize()) break # 解密块并写入 fOut.write(ctx.update(chunk)) processed += len(chunk) # 调度到主线程更新进度 window.after(0, update_progress, processed, enc_file_size) remove(file) os.rename(name + ".tmp", file) # 重置进度 window.after(0, update_progress, 0, 1) window.after(0, lambda: progress_label.config(text="解密完成")) if expvar.get() == 1: os.system("explorer /select," + file) else: tkinter.messagebox.showinfo("解密", "完成!文件已保存到 " + file) except ValueError: window.after(0, lambda: progress_label.config(text="解密失败,密钥错误")) tkinter.messagebox.showerror("错误", "解密失败,密钥错误或文件损坏") except Exception as e: window.after(0, lambda: progress_label.config(text="解密失败")) tkinter.messagebox.showerror("错误", f"解密失败:{str(e)}") def decrypt(): if password == "" or password is None: warn_nokey() else: try: file = easygui.fileopenbox(filetypes=["*.*"], default="*.*") if not file: return progress_label.config(text="正在解密...") # 启动子线程执行解密 threading.Thread(target=decrypt_thread, args=(file,), daemon=True).start() except TypeError: pass def keyprint(): try: tkinter.messagebox.showinfo("密钥", "当前密钥: " + password) except TypeError: warn_nokey() newkey = tk.Button(text="加载密钥", command=pwd) newkey.pack() keygener = tk.Button(text="生成新密钥", command=keygen) keygener.pack() enc = tk.Button(text="加密", command=encrypt) enc.pack() decr = tk.Button(text="解密", command=decrypt) decr.pack() printkey = tk.Button(text="显示当前密钥", command=keyprint) printkey.pack() alert = tk.Text(window, height=5) alert.insert(tk.INSERT, """说明:大文件处理需要一定时间,处理过程中窗口可正常操作,无需等待。 注意:处理过程中请勿关闭程序,否则会导致文件损坏。""") alert.pack(pady=10) if password.strip() == "": warn_nokey() window.mainloop()
补充说明
如果需要同时显示终端tqdm进度条,只需要在子线程的分块处理循环里添加tqdm的更新逻辑即可,导入tqdm后初始化进度条对象,每处理一块更新一次即可。
内容的提问来源于stack exchange,提问作者DoggoPlayer
相关产品推荐
相关产品推荐

