最近研究了一下GUI 图形用户界面(Graphical User Interface),来实现只通过脚本快速打开一个带有UI功能的界面,包括创建一个界面窗口、创建按钮,点击弹窗、文本输入框、下拉框、滚动条、勾选框、分页框架等等。
一、前言
我们在前两次分别实现了用脚本提取文件中的特定内容并保存到文本文件或转表格的功能,链接如下:
1、用Python将 UE5内存分析日志 提取到Excel表_ue5 memreport-CSDN博客,
2、用Python脚本提取文件中想要的内容_python提取文本指定内容-CSDN博客。
然而这些功能在使用时会比较麻烦,因为需要手动修改代码中的文件路径和提取关键字,于是便有了此篇,通过GUI创建一个界面,把这两篇的提取功能汇总到一起,并封装为可视化窗口界面,以达到我们只需在界面中点击按钮选择文件,并输入想要提取的关键字,即可实现上两期的功能了。
二、运行环境
首先是Python脚本运行环境,这个在前两期文章有介绍,而本次运行GUI所用到的库,建议直接学习下面这些文章,有详细介绍GUI的各种功能和配置环境问题。
Python-GUI界面设计(tkinter)_tkinter界面设计-CSDN博客,
三、脚本代码
下面是研究GUI并结合AI辅助实现的功能。直接贴代码。用中文写代码实现如下:
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import random
# 1、提取到表 ------------------------ 分界线 ----------------------
import openpyxl #Excel库
import re #正则表达式
def 获取日志文件(内存日志):
if 内存日志 == "" or 内存日志 == "输入文件路径":
打印记录.insert(tk.END, "\n未选择文件路径!")
return None
try:
日志文件 = open(内存日志, "r", encoding="utf-8")
except FileNotFoundError:
打印记录.insert(tk.END, "\n文件不存在!")
return None
return 日志文件
def 写入表格(写入数据, 首页行数, 提取内存表, 分页目录):
# 通过正则表达式匹配内容类型
记录开始标志 = re.compile(r'MemReport: Begin command "(.*?)"')
记录结束标志 = re.compile(r'MemReport: End command "(.*?)"')
# 根据标志创建新的工作簿
for 行数, 行 in enumerate(写入数据):
if 行数 == 0:
首页 = 提取内存表.worksheets[0]
首页.title = "首页命令"
分页目录['首页'] = 首页
elif 行数 <= 首页行数:
分页目录['首页'].append(行.strip().split(': ')) # 按冒号:分割并写入到表
elif 行数 > 首页行数:
if 'MemReport: Begin ' in 行:
命令内容 = 记录开始标志.search(行).group(1)
if 命令内容:
新页 = 提取内存表.create_sheet()
分页目录[命令内容] = 新页
分页目录[命令内容].title = 命令内容[:31] #旧版Excel表名不超过31个字符
continue
elif 'MemReport: End ' in 行:
命令内容 = 记录结束标志.search(行).group(1)
if 命令内容:
continue
else:
写入的文本 = 行.strip().split(',') # 按逗号,分割字符串后依次写入到表
分页目录[命令内容].append(写入的文本)
def 获取首页总行数(列表):
for i, 行 in enumerate(列表):
if 行.strip() == "" and i != 0:
return i
return len(列表)
def 自适应每列的最大宽度(工作表):
for column in 工作表.columns:
max_length = 0
column = [cell for cell in column]
for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = (max_length + 2)
工作表.column_dimensions[column[0].column_letter].width = adjusted_width
def 调整列宽(目录):
for 工作表 in 目录.values():
自适应每列的最大宽度(工作表)
def 提取到表格(输入文件, 输出文件名, 是否保存到同路径):
日志文件 = 获取日志文件(输入文件)
提取数据 = 日志文件 and 日志文件.readlines() or []
if 提取数据 == []:
打印记录.insert(tk.END, "\n虚幻引擎内存日志文件为空,请选择正确的UE内存分析的日志文件!")
打印记录.yview(tk.END) # 滚动到文本框的底部
return None
提取数据.insert(0, '\n') #在list[0]插入一个空行,方便和日志首行索引的1对齐
首页总行数 = 获取首页总行数(提取数据)
提取内存表 = openpyxl.Workbook() #创建一个Excel表
分页目录 = {}
写入表格(提取数据, 首页总行数, 提取内存表, 分页目录)
调整列宽(分页目录)
# 获取输入文件的目录
if 是否保存到同路径.get():
输入文件目录 = os.path.dirname(输入文件)
打印记录.insert(tk.END, "\n提取结束!\n新文件保存在原文件同目录下。")
else:
输入文件目录 = os.path.dirname(__file__)
打印记录.insert(tk.END, "\n提取结束!\n新文件保存在当前脚本目录下。")
# 删除原文件的后缀并确保输出文件名以 .xlsx 结尾
输出文件名 = os.path.splitext(输出文件名)[0] + '.xlsx'
# 创建输出文件的完整路径
输出文件 = os.path.join(输入文件目录, 输出文件名)
提取内存表.save(输出文件)
# 2、提取到文件 ------------------------ 分界线 ----------------------
def 提取到文件(输入文件, 输出文件名, 匹配字符串, 是否保存到同路径):
日志文件 = 获取日志文件(输入文件)
提取数据 = 日志文件 and 日志文件.readlines() or []
if 提取数据 == []:
return None
extracted_content = []
for i in range(len(提取数据)):
if 匹配字符串 in 提取数据[i]:
extracted_content.append(提取数据[i])
# 获取输入文件的目录
if 是否保存到同路径.get():
输入文件目录 = os.path.dirname(输入文件)
else:
输入文件目录 = os.path.dirname(__file__)
# 创建输出文件的完整路径
输出文件 = os.path.join(输入文件目录, 输出文件名)
with open(输出文件, 'w', encoding='utf-8') as file:
file.writelines(extracted_content)
return extracted_content
def 点击开始提取(输入文件, 输出文件, 匹配字符串, 是否保存到同路径):
提取内容 = 提取到文件(输入文件, 输出文件, 匹配字符串, 是否保存到同路径)
if 提取内容 == [] or 提取内容 == None:
打印记录.insert(tk.END, "\n未提取到内容!")
打印记录.yview(tk.END) # 滚动到文本框的底部
elif 是否保存到同路径.get():
打印记录.delete(1.0, tk.END)
打印记录.insert(tk.END, "\n提取结束!\n新文件保存在原文件同目录下。\n提取到的内容如下:\n" + "\n".join(提取内容))
else:
打印记录.delete(1.0, tk.END)
打印记录.insert(tk.END, "\n提取结束!\n新文件保存在当前脚本目录下。\n提取到的内容如下:\n" + "\n".join(提取内容))
# 3、创建UI界面 ------------------------ 分界线 ----------------------
def 打开文件夹选择对话框(StringVar, Entry):
str = filedialog.askopenfilename() # 打开文件夹选择对话框
StringVar.set(str)
Entry.config(fg='black') # 恢复默认字体颜色
if str:
打印记录.insert(tk.END, "\n提取路径为:" + str)
else:
打印记录.insert(tk.END, "\n未选择文件路径!")
def on_focus_in(event, entry, default_text):
if entry.get() == default_text:
entry.delete(0, tk.END) # 清除提示文本
entry.config(fg='black') # 恢复默认字体颜色
def on_focus_out(event, entry, default_text):
if entry.get() == "":
entry.insert(0, default_text) # 恢复提示文本
entry.config(fg='gray') # 设置提示文本颜色
def 切换颜色主题(*args):
主题 = 下拉项名称.get()
颜色 = {
"红色主题": "red",
"绿色主题": "green",
"蓝色主题": "blue",
"黄色主题": "yellow",
"黑色主题": "black",
"白色主题": "white",
"随机颜色": "#{:02x}{:02x}{:02x}".format(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
}.get(主题, "white")
style.configure('TFrame', background = 颜色) # 设置默认的 TFrame 背景颜色
随机颜色 = "#{:02x}{:02x}{:02x}".format(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
分页1内容框架.config(bg = 随机颜色) # 设置分页1内容框架背景颜色
分页2内容框架.config(bg = 随机颜色)
#二次确认对话框
def 退出程序():
if messagebox.askokcancel("退出", "你确定要退出吗?"):
主界面.destroy()
主界面 = tk.Tk()
主界面.title("提取文件内容_GUI") # 设置窗口标题
主界面.geometry("800x500") # 设置窗口大小
# 创建并配置样式
style = ttk.Style()
style.configure('TNotebook', background='lightyellow') # 设置分页集的背景颜色
style.configure('TNotebook.Tab', background='lightblue') # 设置分页标签的背景颜色
style.configure('TFrame', background='gray') # 设置默认的 TFrame 背景颜色
分页集 = ttk.Notebook(主界面, style='TNotebook')
分页集.pack(expand=1, fill="both", padx=30, pady=30)
# 创建第一个分页UI
分页1 = ttk.Frame(分页集, style="TFrame")
分页集.add(分页1, text="提取到文件")
分页1内容框架 = tk.Frame(分页1, bg="gray") # 使用 Frame 包装分页1的内容,并设置填充
分页1内容框架.pack(expand=1, fill="both", padx=10, pady=10)
输入路径 = tk.StringVar()
输入路径.set("输入文件路径")
输出路径 = "已提取的文件.txt"
匹配关键字 = tk.StringVar()
匹配关键字.set("请输入需要匹配的关键字")
单行输入框 = tk.Entry(分页1内容框架, width=66) # 创建一个单行文本框
单行输入框.place(relx=0.06, rely=0.3) # 设置控件在窗口中的相对位置
单行输入框.config(bd=5, fg='gray', textvariable=输入路径)# StringVar变量,绑定文本后可自动更新文本内容
单行输入框.bind("<FocusIn>", lambda event: on_focus_in(event, 单行输入框, "输入文件路径"))
单行输入框.bind("<FocusOut>", lambda event: on_focus_out(event, 单行输入框, "输入文件路径"))
单行输入框1 = tk.Entry(分页1内容框架, width=66)
单行输入框1.place(relx=0.06, rely=0.4)
单行输入框1.config(bd=5, fg='gray', textvariable=匹配关键字)
单行输入框1.bind("<FocusIn>", lambda event: on_focus_in(event, 单行输入框1, "请输入需要匹配的关键字"))
单行输入框1.bind("<FocusOut>", lambda event: on_focus_out(event, 单行输入框1, "请输入需要匹配的关键字"))
按钮_打开文件夹 = tk.Button(分页1内容框架, text=" 选 择 文 件 ", bg="lightblue")
按钮_打开文件夹.place(relx=0.75, rely=0.3)
按钮_打开文件夹.config(command=lambda:打开文件夹选择对话框(输入路径,单行输入框))
按钮_提取到文件 = tk.Button(分页1内容框架, text="提取到文件", width=15, height=2, bg="lightblue", font=("Helvetica", 14))
按钮_提取到文件.place(relx=0.75, rely=0.4)
按钮_提取到文件.config(command=lambda:点击开始提取(输入路径.get(), 输出路径, 匹配关键字.get(), 是否保存到同路径))
# 创建第二个分页
分页2 = ttk.Frame(分页集, style="TFrame")
分页集.add(分页2, text="提取到表格")
分页2内容框架 = tk.Frame(分页2, bg="gray") # 相当于在分页2中再创建一个框架
分页2内容框架.pack(expand=1, fill="both", padx=10, pady=10)
单行输入框2 = tk.Entry(分页2内容框架, width=50) # 创建一个单行文本框
单行输入框2.place(relx=0.06, rely=0.3) # 使用 pack 布局管理器,并设置 pady 参数
单行输入框2.config(bd=5, fg='gray', textvariable=输入路径)# StringVar变量,绑定文本后可自动更新文本内容
单行输入框2.bind("<FocusIn>", lambda event: on_focus_in(event, 单行输入框2, "输入文件路径"))
单行输入框2.bind("<FocusOut>", lambda event: on_focus_out(event, 单行输入框2, "输入文件路径"))
按钮_打开文件夹2 = tk.Button(分页2内容框架, text=" 选 择 文 件 ", bg="lightblue")
按钮_打开文件夹2.place(relx=0.65, rely=0.3)
按钮_打开文件夹2.config(command=lambda:打开文件夹选择对话框(输入路径,单行输入框2))
按钮_提取到表 = tk.Button(分页2内容框架, text="提取到表格", width=15, height=2, bg="lightblue", font=("Helvetica", 14))
按钮_提取到表.place(relx=0.65, rely=0.4)
按钮_提取到表.config(command=lambda:提取到表格(输入路径.get(), 输出路径, 是否保存到同路径))
#创建下拉框UI
下拉项名称 = tk.StringVar()
下拉项名称.set("切换颜色主题")
下拉列表 = ["红色主题", "绿色主题", "蓝色主题", "黄色主题", "黑色主题", "白色主题", "随机颜色"]
下拉框 = tk.OptionMenu(主界面, 下拉项名称, *下拉列表)
下拉框.config(width=10)
下拉框.place(relx=0.78, rely=0.8)
下拉项名称.trace_add("write", 切换颜色主题) # 绑定下拉框选择事件
# 创建勾选框
是否保存到同路径 = tk.BooleanVar() # 创建一个布尔类型的变量
是否保存到同路径.set(True) # 默认勾选
勾选框 = tk.Checkbutton(主界面, text="保存到原文件位置", variable=是否保存到同路径)
勾选框.place(relx=0.78, rely=0.7)
# 创建多行文本框、滚动条
打印记录 = tk.Text(主界面, wrap="word", height=7, width=70)
滚动条 = tk.Scrollbar(主界面, command=打印记录.yview)
打印记录.config(yscrollcommand=滚动条.set)
打印记录.place(relx=0.1, rely=0.7)
滚动条.place(relx=0.73, rely=0.7, relheight=0.2)
# 创建退出按钮
退出按钮 = tk.Button(主界面, text="退出", command=退出程序)
退出按钮.place(relx=0.9, rely=0.13)
主界面.mainloop()
四、运行结果
我们执行上面的Python脚本后,会得到如下界面:
这个界面可以切换2个分页,分别对应的是前面提到的两个功能。第一个分页功能是将文件的内容按照关键字提取到文本文件中,第二个是将虚幻引擎生成的内存分析报告的日志文件提取到Excel表格中。这两页面的功能都是提取文字字符串,但功能略有不同。
此篇是以创建GUI窗口功能为主要目的,在实现这两个功能后,顺带扩展了一下其他一些常用的GUI功能。包括创建一个界面窗口、创建按钮,点击弹窗、文本输入框、下拉框、滚动条、勾选框、分页框架等等。例如:
1、点击下拉框可以切换主题,根据分页的样式修改为随机颜色。
2、点击退出按钮会弹窗提示二次确认。
3、勾选框可以实现一个布尔值变量来控制提取后的文件保存在哪里。
4、打印执行日志功能,如果打印内容很多,还可以用滚动框来下滑文本区内容。