Python打包窗口图标丢失修复工具

工具介绍

工具名称:Python打包窗口图标丢失修复工具(PackTool)

工具版本:1.01

功能介绍:

1. 能够将ico文件转换为二进制内容,然后将二进制内容保存在py文件中

2. 能够将py文件中对应的二进制内容转换为ico文件

3. 能够作为API进行直接调用

工具截图:

下载链接:GitHub下载链接  百度网盘下载链接(提取码:1314)

源代码:

import os
import base64
import tkinter as tk
import tkinter.filedialog as tf
import tkinter.messagebox as tm
import random
import warnings
from win32api import SetFileAttributes
from win32con import FILE_ATTRIBUTE_HIDDEN
from subprocess import Popen, PIPE, STDOUT
from sys import executable as EXECUTABLE
import Tool.MyLogger as ml
from ctypes import windll


class PackTool:
    __filePath = ""
    __execPath = ""
    __invokeName = ""
    __pyName = ""
    __tips = ('请选择ICO文件并输入ICO调用变量名称和生成的py文件名称', '生成的py文件与ICO文件位于同一目录下', '可以使用数据管理来清空py文件中的内容', '若ICO调用变量名称已存在会提示你是否覆盖')
    __site = [('数据打包', 0), ('数据管理', 1), ('数据解码', 2)]
    __log = ml.IMyLogger()
    __endFlag = False  # IcoBase64Decoder()执行完成的标志
    __callNum = 5  # IcoSetToTk()递归调用的次数
    __isDirect = False  # 是否直接调用API

    # 创建快捷方式
    def _createShortcut(self):
        v_execpath = os.path.dirname(EXECUTABLE)
        self.__execPath = v_execpath.replace('\\', '/')
        v_batpath = v_execpath + "\\init.bat"
        if os.path.exists(v_batpath):
            self.__log.InLog("Create shortcut successful!")
            startcmd = "start " + "shortcut.bat"
            startpath = v_execpath + "\\shortcut.bat"
            cmd = "cd " + v_execpath + " && " + startcmd
            pop = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT)
            pop.wait()
            if pop.returncode == 0:
                pop.terminate()
                pop.kill()
                SetFileAttributes(startpath, FILE_ATTRIBUTE_HIDDEN)

    def _windowSize(self, p_width, p_height):
        size = '%dx%d+%d+%d' % (
            p_width, p_height, (self.__screenWidth - p_width) / 2, (self.__screenHeight - p_height) / 2)
        return size

    def __init__(self, p_activeTk=True):
        # 打包时请取消以下这句代码的注释
        self._createShortcut()
        # 告诉操作系统使用程序自身的dpi适配
        windll.shcore.SetProcessDpiAwareness(1)
        # 获取屏幕的缩放因子
        ScaleFactor = windll.shcore.GetScaleFactorForDevice(0)
        self.__root = tk.Tk()
        self.__root.protocol("WM_DELETE_WINDOW", func=lambda: self._ThreadClose())
        self.__root.title('打包工具')
        # 获取屏幕宽高
        self.__screenWidth = self.__root.winfo_screenwidth()
        self.__screenHeight = self.__root.winfo_screenheight()
        # 设置窗口大小
        self.__root.geometry(self._windowSize(int(self.__screenWidth * 0.35), int(self.__screenHeight * 0.625)))
        # 设置程序缩放
        self.__root.tk.call('tk', 'scaling', ScaleFactor / 75)

        self.__v = tk.IntVar()

        self.__Fr_m = tk.Frame(self.__root)
        self.__Fr_m.pack()

        self.__Fr_subm1 = tk.Frame(self.__Fr_m)
        for name, index in self.__site:
            Rb = tk.Radiobutton(self.__Fr_subm1, text=name, variable=self.__v, value=index,
                                command=lambda: self._ChangeMode())
            Rb.pack(side='left', padx='5px', pady='5px')
        self.__Fr_subm1.pack()

        self.__Fr_subm2 = tk.Frame(self.__Fr_m)
        self.__Lb_1 = tk.Label(self.__Fr_subm2, text='Tip:请选择ICO文件并输入ICO调用变量名称和生成的py文件名称', fg='blue')
        self.__Lb_1.pack(pady='5px')
        self.__Fr_subm2.pack(pady='5px')

        self.__Fr_sub1 = tk.Frame(self.__Fr_m)
        self.__Fr_sub1.pack()

        self.__Fr_a1 = tk.Frame(self.__Fr_sub1)
        self.__Lb_a1 = tk.Label(self.__Fr_a1, text='请选择.ico文件', fg='red')
        self.__Lb_a1.pack(pady='5px')
        self.__Lb_a2 = tk.Label(self.__Fr_a1, text='')
        self.__Lb_a2.pack(pady='5px')
        self.__Bt_a1 = tk.Button(self.__Fr_a1, text='选择文件', width=20, command=lambda: self._ChooseFile())
        self.__Bt_a1.pack(pady='5px')
        self.__Fr_a1.pack(pady='5px')

        self.__Fr_a2 = tk.Frame(self.__Fr_sub1)
        self.__Lb_a3 = tk.Label(self.__Fr_a2, text='请输入ICO调用变量名称', fg='red')
        self.__Lb_a3.pack(pady='5px')
        self.__Et_a1 = tk.Entry(self.__Fr_a2, exportselection=0)
        self.__Et_a1.pack(pady='5px')
        self.__Lb_a4 = tk.Label(self.__Fr_a2, text='请输入生成的.py文件名称', fg='red')
        self.__Lb_a4.pack(pady='5px')
        self.__Et_a2 = tk.Entry(self.__Fr_a2, exportselection=0)
        self.__Et_a2.pack(pady='5px')
        self.__Bt_a2 = tk.Button(self.__Fr_a2, text='生成ICO的py文件', width=20, command=lambda: self._IcoToBase64())
        self.__Bt_a2.pack(pady='5px')
        self.__Fr_a2.pack(pady='5px')

        self.__Fr_sub2 = tk.Frame(self.__Fr_m)
        self.__Fr_sub2.pack_forget()

        self.__Fr_b1 = tk.Frame(self.__Fr_sub2)
        self.__Lb_b1 = tk.Label(self.__Fr_b1, text='请选择.py文件', fg='red')
        self.__Lb_b1.pack(pady='5px')
        self.__Lb_b2 = tk.Label(self.__Fr_b1, text='')
        self.__Lb_b2.pack(pady='5px')
        self.__Bt_b1 = tk.Button(self.__Fr_b1, text='选择文件', width=20, command=lambda: self._ChooseFile())
        self.__Bt_b1.pack(pady='5px')
        self.__Fr_b1.pack(pady='5px')

        self.__Fr_b2 = tk.Frame(self.__Fr_sub2)
        self.__Bt_b2 = tk.Button(self.__Fr_b2, text='清空py文件内容', width=20, command=lambda: self._ClearPyContent())
        self.__Bt_b2.pack(pady='5px')
        self.__Fr_b2.pack(pady='5px')

        self.__Fr_sub3 = tk.Frame(self.__Fr_m)
        self.__Fr_sub3.pack()

        self.__Fr_c1 = tk.Frame(self.__Fr_sub3)
        self.__Lb_c1 = tk.Label(self.__Fr_c1, text='请选择.py文件', fg='red')
        self.__Lb_c1.pack(pady='5px')
        self.__Lb_c2 = tk.Label(self.__Fr_c1, text='')
        self.__Lb_c2.pack(pady='5px')
        self.__Bt_c1 = tk.Button(self.__Fr_c1, text='选择文件', width=20, command=lambda: self._ChooseFile())
        self.__Bt_c1.pack(pady='5px')
        self.__Fr_c1.pack(pady='5px')

        self.__Fr_c2 = tk.Frame(self.__Fr_sub3)
        self.__Lb_c3 = tk.Label(self.__Fr_c2, text='请输入ICO调用变量名称', fg='red')
        self.__Lb_c3.pack(pady='5px')
        self.__Et_c1 = tk.Entry(self.__Fr_c2, exportselection=0)
        self.__Et_c1.pack(pady='5px')
        self.__Lb_c4 = tk.Label(self.__Fr_c2, text='请输入生成的.ico文件名称', fg='red')
        self.__Lb_c4.pack(pady='5px')
        self.__Et_c2 = tk.Entry(self.__Fr_c2, exportselection=0)
        self.__Et_c2.pack(pady='5px')
        self.__Bt_c2 = tk.Button(self.__Fr_c2, text='数据解压', width=20,
                                 command=lambda: self._DataDecoder())
        self.__Bt_c2.pack(pady='5px')
        self.__Fr_c2.pack(pady='5px')

        self.IcoBase64Encoder(self.__execPath + '/image/包1.ico', 'pak1', 'ico.py',
                              self.__execPath + '/build')
        self.IcoBase64Decoder('packTool', p_icoVariableName='pak1',
                              p_pyPath=self.__execPath + '/build/ico.py')
        self.IcoSetToTk(self.__root, 'packTool', self.__execPath + '/build')
        if p_activeTk is True:
            self.__isDirect = False
            self.__root.mainloop()

    # ICO文件转二进制内容,并保存为py文件
    def _IcoToBase64(self, p_icoPath=None, p_icoVariableName=None, p_pyName=None, p_pyFolderPath=None):
        v_isCheck = True
        if self.__isDirect is False:
            v_isCheck = self._InputCheckAndDeal()
        if v_isCheck:
            v_path = self.__filePath
            v_invokeName = self.__invokeName
            v_pyName = self.__pyName
            v_pyPath = v_pyName + ".py"
            if self.__isDirect is True:
                v_path = p_icoPath
                v_invokeName = p_icoVariableName
                v_pyName = p_pyName
                v_pyPath = p_pyFolderPath + '/' + v_pyName + '.py'
            if os.path.exists(v_path):
                # 打开ico文件并转换为二进制
                open_icon = open(v_path, "rb")
                b64str = base64.b64encode(open_icon.read())
                open_icon.close()
                # 将内容写入py文件中
                v_content = " = %s" % b64str
                write_data = v_invokeName + v_content + "\n"
                v_repeateValue = self._IsIcoRepeate(v_pyPath, p_invokeName=v_invokeName, p_content=v_content)
                if v_repeateValue is True:
                    f = open(v_pyPath, "a+")
                    f.write(write_data)
                    f.close()
                    if self.__isDirect is False:
                        self.__Lb_1.config(text='生成' + v_pyName + '.py文件成功!')
                # 对生成的py文件内容进行查重
                v_data = self._IcoRepeateCheck(v_pyPath)
                v_dataStr = ""
                for d in v_data:
                    v_dataStr += d
                if v_dataStr != "":
                    f = open(v_pyPath, "w+")
                    f.write(v_dataStr)
                    f.close()
            else:
                if self.__isDirect is True:
                    warnings.warn('根据文件路径所查找的文件不存在!', stacklevel=4)
                else:
                    tm.showwarning('Warning', '根据文件路径所查找的文件不存在!')
                    self._RandomTip()

    # ICO的py文件二进制解码器,将生成一个指定名称的ico文件
    def IcoBase64Encoder(self, p_icoPath, p_icoVariableName, p_pyName, p_pyFolderPath):
        v_pyName = p_pyName.replace('.py', '')
        self.__isDirect = True
        self._IcoToBase64(p_icoPath, p_icoVariableName, v_pyName, p_pyFolderPath)

    # ICO的py文件二进制解码器,将生成一个指定名称的ico文件
    def IcoBase64Decoder(self, p_icoName, p_icoBytesVariable=None, p_icoVariableName=None, p_pyPath=None):
        self.__isDirect = True
        self._Base64ToIco(p_icoName, p_icoBytesVariable, p_icoVariableName, p_pyPath)

    def _Base64ToIco(self, p_icoName, p_icoBytesVariable=None, p_icoVariableName=None, p_pyPath=None):
        v_icoPath = p_pyPath.replace(p_pyPath.split('/')[-1], '')
        v_icoPath = v_icoPath + p_icoName.replace('.ico', '') + ".ico"
        picture = open(v_icoPath, "wb+")
        if p_icoBytesVariable is not None:
            picture.write(base64.b64decode(p_icoBytesVariable))
        elif p_icoVariableName is not None and p_pyPath is not None:
            v_content = self._GetContentByName(p_icoVariableName, p_pyPath)
            if v_content is None:
                warnings.warn('对应的py文件中不存在ICO调用变量名称:' + p_icoVariableName, stacklevel=3)
                return
            else:
                v_icoVariable = v_content[2:len(v_content) - 2]
                picture.write(base64.b64decode(v_icoVariable))
        picture.flush()
        os.fsync(picture.fileno())
        picture.close()
        self.__endFlag = True

    # 给将要打包的Tk()设置Ico
    def IcoSetToTk(self, p_tk, p_icoName, p_pyFolderPath):
        self.__isDirect = True
        self._IcoSet(p_tk, p_icoName, p_pyFolderPath)

    def _IcoSet(self, p_tk, p_icoName, p_pyFolderPath):
        self.__callNum -= 1
        if self.__endFlag:
            # 获得生成的ico文件的绝对路径
            v_icoName = p_icoName.replace('.ico', '') + '.ico'
            v_path = p_pyFolderPath + '/' + v_icoName
            if p_pyFolderPath[-1] == '/':
                v_path = p_pyFolderPath + v_icoName
            # 判断路径下ico文件是否存在
            if os.path.exists(v_path):
                # 对路径进行转码和解码操作
                v_path = v_path.encode("utf-8")
                v_path = v_path.decode("utf-8", "strict")
                # 设置Tk()的窗口图标,报错的就是下面这一行
                p_tk.iconbitmap(default=v_path)
                os.remove(v_path)
            else:
                if self.__isDirect is True:
                    warnings.warn('二进制解码生成的ico文件不存在!', stacklevel=3)
                    # 录入日志信息
                    self.__log.InLog("Warning:二进制解码生成的ico文件不存在!")
                else:
                    tm.showwarning('Warning', '二进制解码生成的ico文件不存在!')
        else:
            if self.__callNum > 0:
                self._IcoSet(p_tk, p_icoName, p_pyFolderPath)
            else:
                self.__log.InLog("IcoSetToTk递归结束")
                self.__endFlag = False
                self.__callNum = 5

    # 针对ImgToBase64方法中的查重检测,返回是否重复的信息
    def _IsIcoRepeate(self, p_path, **p_Names):
        f = open(p_path, 'a')
        f.close()
        f = open(p_path, 'r')
        v_invokeName = p_Names['p_invokeName']
        v_contents = f.readlines()
        self._DealValueOfList(v_contents, '\n')
        list_names = list()
        list_contents = list()
        for c in v_contents:
            s1 = c.replace(' ', '')
            s = s1.split('=', 1)
            list_names.append(s[0])
            list_contents.append(s[1])
        if list_names.count(v_invokeName) > 0:
            if self.__isDirect is False:
                isRight = tm.askyesno('Warning', '输入的ICO调用变量名称已存在,是否覆盖?')
                return isRight
        return True

    # 针对ImgToBase64方法中的查重检测,返回去重后的数据列表
    def _IcoRepeateCheck(self, p_path):
        f = open(p_path, 'r')
        v_contents = f.readlines()
        self._DealValueOfList(v_contents, '\n')
        list_names = list()
        list_contents = list()
        list_nc = list()
        for c in v_contents:
            s = c.replace(' ', '')
            s = s.split('=', 1)
            list_names.append(s[0])
            list_contents.append(s[1])
        list_indexN = self._DealRepeateValueOfList(list_names)
        list_indexC = self._DealRepeateValueOfList(list_contents)
        # 名称集和数据集重复元素的索引求并集
        for i in list_indexN:
            if i not in list_indexC:
                list_indexC.append(i)
        for i in list_indexC:
            if i not in list_indexN:
                list_indexN.append(i)
        #  对重复元素进行删除
        for i in list_indexN:
            del list_names[i]
        for i in list_indexC:
            del list_contents[i]
        #  重新对数据进行拼接
        if len(list_names) > 0 and len(list_contents) > 0:
            for i in range(0, len(list_names)):
                list_nc.append(list_names[i] + " = " + list_contents[i])
        return list_nc

    # 用于检测列表中的重复数据,返回重复数据的索引列表
    def _DealRepeateValueOfList(self, p_list):
        list_index = list()
        for i in range(0, len(p_list)):
            for j in range(i + 1, len(p_list)):
                if p_list[i] == p_list[j]:
                    list_index.append(i)
                    break
        return list_index

    # 用于剔除列表中的指定数据
    def _DealValueOfList(self, p_list, *p_data):
        for d in p_data:
            while p_list.count(d) > 0:
                p_list.remove(d)

    # 用于开启选择文件窗口
    def _ChooseFile(self):
        self._RandomTip()
        self.__filePath = tf.askopenfilename()
        if self.__v.get() == 0:
            self.__Lb_a2.config(text=self.__filePath)
        elif self.__v.get() == 1:
            self.__Lb_b2.config(text=self.__filePath)
        elif self.__v.get() == 2:
            self.__Lb_c2.config(text=self.__filePath)

    # 用于清空上功能模块中共用的内容
    def _ClearShareContent(self):
        self.__filePath = ''
        self._RandomTip()

    # 用于在关闭窗口时关闭该应用线程
    def _ThreadClose(self):
        self.__root.quit()
        self.__root.destroy()

    # 用于清除py文件中的内容
    def _ClearPyContent(self):
        v_path = self.__filePath
        v_pyName = self.__pyName
        if os.path.exists(v_path):
            f = open(v_path, 'w')
            f.write('')
            f.close()
            self.__Lb_1.config(text='清空' + v_pyName + '.py' + '文件成功')
        else:
            tm.showwarning('Warning', '请选择待清空的.py文件')

    # 根据ico调用变量名称获取其二进制内容
    def _GetContentByName(self, p_icoVariableName, p_path):
        if os.path.exists(p_path):
            f = open(p_path, 'r')
            v_contents = f.readlines()
            self._DealValueOfList(v_contents, '\n')
            list_names = list()
            list_contents = list()
            for c in v_contents:
                s = c.replace(' ', '')
                s = s.split('=', 1)
                list_names.append(s[0])
                list_contents.append(s[1])
            f.close()
            for i in range(0, len(list_contents)):
                if list_names[i] == p_icoVariableName:
                    return list_contents[i]
        else:
            warnings.warn('路径' + p_path + '所对应的py文件不存在!', stacklevel=4)
        return None

    # 数据解压
    def _DataDecoder(self):
        v_icoName = self.__Et_c2.get()
        v_icoVariableName = self.__Et_c1.get()
        v_path = self.__filePath
        v_content = self._GetContentByName(v_icoVariableName, v_path)
        if v_content is None:
            tm.showwarning('Warning', '所选择的py文件中不存在ICO调用变量名称:' + v_icoVariableName)
        else:
            v_icoVariable = v_content[2:len(v_content) - 2]
            self._Base64ToIco(v_icoName, v_icoVariable)
            self.__Lb_1.config(text='数据解压成功!')

    # 用于更换功能模块
    def _ChangeMode(self):
        self._RandomTip()
        self._ClearShareContent()
        if self.__v.get() == 0:
            self.__Fr_sub1.pack()
            self.__Fr_sub2.pack_forget()
            self.__Fr_sub3.pack_forget()
        elif self.__v.get() == 1:
            self.__Fr_sub1.pack_forget()
            self.__Fr_sub3.pack_forget()
            self.__Fr_sub2.pack()
        elif self.__v.get() == 2:
            self.__Fr_sub1.pack_forget()
            self.__Fr_sub2.pack_forget()
            self.__Fr_sub3.pack()

    # 字符串中是否包括中文(编码集UTF-8),包括则返回True,反之则返回False
    def _IsIncludeChinese(self, p_str):
        for s in p_str:
            if u'\u4e00' <= s <= u'\u9fa5':
                return True
        return False

    # 输入检测和处理
    def _InputCheckAndDeal(self):
        self.__invokeName = str(self.__Et_a1.get()).strip()
        self.__pyName = str(self.__Et_a2.get()).strip()
        v_path = self.__filePath
        v_invokeName = self.__invokeName
        v_pyName = self.__pyName
        if v_path == "":
            tm.showwarning('Warning', '请选择文件路径!')
            self._RandomTip()
            return False
        if v_path.split('/')[-1].split('.')[-1] != 'ico':
            tm.showwarning('Warning', '请选择.ico文件!')
            self._RandomTip()
            return False
        if v_invokeName == "":
            tm.showwarning('Warning', '输入的ICO调用变量名称为空!')
            self._RandomTip()
            return False
        if v_invokeName.isalnum() is False or self._IsIncludeChinese(
                v_invokeName) is True or v_invokeName.isnumeric() is True:
            tm.showwarning('Warning', '输入的ICO调用变量名称必须满足以下条件:\n1.不包括除字母或数字外的其它字符\n2.不能为纯数字')
            self._RandomTip()
            return False
        if v_pyName == "":
            tm.showwarning('Warning', '输入的生成的.py文件名称为空!')
            self._RandomTip()
            return False
        if v_pyName.isalnum() is False or self._IsIncludeChinese(v_pyName) is True:
            tm.showwarning('Warning', '输入的生成的.py文件名称不能包括除字母或数字外的其它字符!')
            self._RandomTip()
            return False
        elif '.py' in v_pyName:
            self.__pyName = v_pyName.replace('.py', '')
        return True

    def _RandomTip(self):
        i = random.randint(0, len(self.__tips) - 1)
        self.__Lb_1.config(text='Tip:' + self.__tips[i])


class IPackTool:
    def __init__(self, p_activeTk=True):
        self.__pk = PackTool(p_activeTk)

    def IcoBase64Decoder(self, p_icoName, p_icoBytesVariable=None, p_icoVariableName=None, p_pyPath=None):
        self.__pk.IcoBase64Decoder(p_icoName, p_icoBytesVariable, p_icoVariableName, p_pyPath)

    def IcoSetToTk(self, p_tk, p_icoName, p_pyFolderPath):
        self.__pk.IcoSetToTk(p_tk, p_icoName, p_pyFolderPath)

    def IcoBase64Encoder(self, p_icoPath, p_icoVariableName, p_pyName, p_pyFolderPath):
        self.__pk.IcoBase64Encoder(p_icoPath, p_icoVariableName, p_pyName, p_pyFolderPath)


# 打包后请取消此行代码的注释
mIPT = IPackTool()

如果这篇文章对你有帮助,请给作者点个赞吧!

猜你喜欢

转载自blog.csdn.net/hgf1037882434/article/details/128426036