You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 03:15:02