Python项目75:PyInstaller+Tkinter+subprocess打包工具1.0(安排 !!)

这个打包工具包含以下功能:

1.主要功能:选择Python脚本文件,设置打包选项(单文件打包、无控制台窗口),自定义程序图标,指定输出目录,实时显示打包日志。
2.自适应布局改进:使用grid布局替代部分pack布局,增强布局控制,为所有容器设置grid_columnconfigure和grid_rowconfigure的weight参数,为Entry组件添加sticky="ew"使其可以水平扩展,日志区域使用nsew粘性设置,配合滚动条实现双向扩展,设置窗口最小尺寸限制minsize(600, 400)。
3.使用说明:选择要打包的Python脚本文件(.py),根据需要设置打包选项(可选)选择程序图标(.ico),(可选)添加版本信息文件选择组件(支持.rc,.txt文件),(可选)指定输出目录,点击"开始打包"按钮启动打包过程。

4.注意事项:电脑上需要提前安装PyInstaller:pip install pyinstaller,输出目录默认使用PyInstaller的默认设置(当前目录下的dist文件夹)。在这里插入图片描述

# -*- coding: utf-8 -*-
# @Author : 小红牛
# 微信公众号:wdPython
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import subprocess
import os
import threading

class PyInstallerPacker:
    def __init__(self, root):
        self.root = root
        root.title("PyInstaller打包exe工具1.0")
        root.geometry("720x550")
        root.minsize(640, 480)

        # 配置根窗口网格布局
        root.grid_columnconfigure(0, weight=1)
        root.grid_rowconfigure(0, weight=1)

        # 样式配置
        self.style = ttk.Style()
        self.style.configure("TButton", padding=6)
        self.style.configure("TLabel", padding=3)
        self.style.configure("TEntry", padding=3)

        # 主框架
        main_frame = ttk.Frame(root, padding=15)
        main_frame.grid(row=0, column=0, sticky="nsew")
        main_frame.grid_columnconfigure(1, weight=1)
        main_frame.grid_rowconfigure(3, weight=1)

        # 脚本选择组件
        ttk.Label(main_frame, text="Python脚本路径:").grid(
            row=0, column=0, sticky="w", pady=3)
        self.script_entry = ttk.Entry(main_frame)
        self.script_entry.grid(row=0, column=1, sticky="ew", padx=5)
        ttk.Button(main_frame, text="浏览",
                   command=self.browse_script).grid(row=0, column=2, padx=5)

        # 打包选项框架
        options_frame = ttk.LabelFrame(main_frame, text="打包选项", padding=10)
        options_frame.grid(row=1, column=0, columnspan=3,
                           pady=10, sticky="ew")
        options_frame.grid_columnconfigure(1, weight=1)

        # 打包选项内容
        self.onefile_var = tk.BooleanVar()
        ttk.Checkbutton(options_frame, text="单文件打包",
                        variable=self.onefile_var).grid(
            row=0, column=0, sticky="w", padx=5)

        self.noconsole_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(options_frame, text="无控制台窗口",
                        variable=self.noconsole_var).grid(
            row=0, column=1, sticky="w", padx=5)

        # 图标设置
        ttk.Label(options_frame, text="程序图标:").grid(
            row=1, column=0, sticky="w", pady=5)
        self.icon_entry = ttk.Entry(options_frame)
        self.icon_entry.grid(row=1, column=1, sticky="ew", padx=5)
        ttk.Button(options_frame, text="选择图标",
                   command=self.browse_icon, width=10).grid(row=1, column=2)

        # 版本信息设置
        ttk.Label(options_frame, text="版本信息文件:").grid(
            row=2, column=0, sticky="w", pady=5)
        self.version_entry = ttk.Entry(options_frame)
        self.version_entry.grid(row=2, column=1, sticky="ew", padx=5)
        ttk.Button(options_frame, text="选择文件",
                   command=self.browse_version, width=10).grid(row=2, column=2)

        # 输出目录设置
        ttk.Label(main_frame, text="输出目录:").grid(
            row=2, column=0, sticky="w", pady=3)
        self.output_entry = ttk.Entry(main_frame)
        self.output_entry.grid(row=2, column=1, sticky="ew", padx=5)
        ttk.Button(main_frame, text="选择目录",
                   command=self.browse_output).grid(row=2, column=2, padx=5)

        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="打包日志", padding=5)
        log_frame.grid(row=3, column=0, columnspan=3,
                       pady=10, sticky="nsew")
        log_frame.grid_rowconfigure(0, weight=1)
        log_frame.grid_columnconfigure(0, weight=1)

        self.log_text = tk.Text(log_frame, wrap=tk.WORD,
                                font=('Consolas', 10))
        scrollbar = ttk.Scrollbar(log_frame, orient="vertical",
                                  command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        self.log_text.grid(row=0, column=0, sticky="nsew")
        scrollbar.grid(row=0, column=1, sticky="ns")

        # 打包按钮
        self.pack_btn = ttk.Button(main_frame, text="开始打包",
                                   command=self.start_packing)
        self.pack_btn.grid(row=4, column=1, pady=10)

    def browse_script(self):
        """选择Python脚本文件"""
        path = filedialog.askopenfilename(
            filetypes=[("Python文件", "*.py"), ("所有文件", "*.*")])
        if path:
            self.script_entry.delete(0, tk.END)
            self.script_entry.insert(0, os.path.normpath(path))

    def browse_icon(self):
        """选择图标文件"""
        path = filedialog.askopenfilename(
            filetypes=[("图标文件", "*.ico"), ("所有文件", "*.*")])
        if path:
            self.icon_entry.delete(0, tk.END)
            self.icon_entry.insert(0, os.path.normpath(path))

    def browse_version(self):
        """选择版本信息文件"""
        path = filedialog.askopenfilename(
            filetypes=[("资源文件", "*.rc *.txt"), ("所有文件", "*.*")])
        if path:
            self.version_entry.delete(0, tk.END)
            self.version_entry.insert(0, os.path.normpath(path))

    def browse_output(self):
        """选择输出目录"""
        path = filedialog.askdirectory()
        if path:
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, os.path.normpath(path))

    def log_message(self, message):
        """在日志区域显示消息"""
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()

    def start_packing(self):
        """启动打包过程"""
        script_path = self.script_entry.get()
        if not script_path:
            messagebox.showerror("错误", "请选择要打包的Python脚本!")
            return
        if not os.path.exists(script_path):
            messagebox.showerror("错误", "指定的Python脚本不存在!")
            return

        # 构建PyInstaller命令
        cmd = ["pyinstaller", "--clean"]

        # 添加基本选项
        if self.onefile_var.get():
            cmd.append("--onefile")
        if self.noconsole_var.get():
            cmd.append("--onefile")
            cmd.append("--noconsole")

        # 添加图标
        if icon_path := self.icon_entry.get():
            if not os.path.exists(icon_path):
                messagebox.showerror("错误", "指定的图标文件不存在!")
                return
            cmd.extend(["--icon", f'"{icon_path}"'])

        # 添加版本信息
        if version_path := self.version_entry.get():
            if not os.path.exists(version_path):
                messagebox.showerror("错误", "版本信息文件不存在!")
                return
            cmd.extend(["--version-file", f'"{version_path}"'])

        # 添加输出目录
        if output_dir := self.output_entry.get():
            cmd.extend(["--distpath", f'"{output_dir}"'])

        cmd.append(f'"{script_path}"')

        # 初始化界面状态
        self.pack_btn.config(state=tk.DISABLED)
        self.log_text.delete(1.0, tk.END)
        self.log_message(">>> 开始打包命令: " + " ".join(cmd))

        # 启动后台线程执行打包命令
        threading.Thread(
            target=self.run_pack_command,
            args=(cmd,),
            daemon=True
        ).start()

    def run_pack_command(self, cmd):
        """执行打包命令"""
        try:
            process = subprocess.Popen(
                " ".join(cmd),
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                shell=True,
                encoding="utf-8",
                errors="replace"
            )

            # 实时读取输出
            while True:
                output = process.stdout.readline()
                if output == "" and process.poll() is not None:
                    break
                if output:
                    self.root.after(0, self.log_message, output.strip())

            # 处理返回结果
            return_code = process.poll()
            if return_code == 0:
                self.root.after(0, self.log_message, ">>> 打包成功完成!")
                self.root.after(0, messagebox.showinfo,
                                "完成", "打包操作成功完成!")
            else:
                error_msg = f">>> 打包失败,错误码:{return_code}"
                self.root.after(0, self.log_message, error_msg)
                self.root.after(0, messagebox.showerror,
                                "错误", f"打包失败,错误码:{return_code}")

        except Exception as e:
            error_msg = f">>> 发生异常错误:{str(e)}"
            self.root.after(0, self.log_message, error_msg)
            self.root.after(0, messagebox.showerror,
                            "异常错误", f"发生未预期错误:{str(e)}")
        finally:
            self.root.after(0, self.pack_btn.config, {"state": tk.NORMAL})


if __name__ == "__main__":
    root = tk.Tk()
    app = PyInstallerPacker(root)
    root.mainloop()

完毕!!感谢您的收看

----------★★跳转到历史博文集合★★----------
我的零基础Python教程,Python入门篇 进阶篇 视频教程 Py安装py项目 Python模块 Python爬虫 Json Xpath 正则表达式 Selenium Etree CssGui程序开发 Tkinter Pyqt5 列表元组字典数据可视化 matplotlib 词云图 Pyecharts 海龟画图 Pandas Bug处理 电脑小知识office自动化办公 编程工具 NumPy Pygame

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值