这是一个系列文章。下一篇【【三】把Python Tk GUI打包exe可执行程序,移植到其他机器可用】
使用python脚本写一个小工具。因为命令行运行的使用会有dos窗口,交互也不是很方便,开发环境运行也不方便分享给别人用,所以想到使用一个简单、易学、好上手的GUI工具给包装一下,达到一个直观、易用的目的,也可以打包分享给别人。
main_tk.py
# -*- coding: UTF-8 -*-
from tkinter import Tk
from tkinter import Frame # frame子页面
from tkinter.messagebox import askyesno
from tkinter.ttk import Notebook # Tab标签
from threading import Thread
from sys import exit
"""
首行配置输出中文乱码
引入tk依赖:最好使用from的方式引入,避免后续打包的时候会加入很多无用的包,造成运行文件过大
"""
class MainTk:
"""MainTk: 主窗口配置对象"""
_root = None
"""私有成员变量,不能通过对象点的方式获得,必须通过get方式获取"""
_thread = None
"""线程对象"""
def __init__(self, root: Tk):
self._root = root
"""设置窗口大小和位置"""
width = 700 # 窗口宽度
height = 500 # 窗口高度
position_x = 300 # 窗口距离屏幕左边的水平距离
position_y = 100 # 窗口距离屏幕右边的水平距离
geometry = str(width) + 'x' + str(height) + '+' + str(position_x) + '+' + str(position_y)
self._root.geometry(geometry)
"""设置窗口标题"""
self._root.title('Tools')
"""设置窗口图标:图标路径,我这里logo放在运行main的当前目录"""
self._root.iconbitmap('logo.ico')
"""点击右上角关闭窗口的监听"""
self._root.protocol("WM_DELETE_WINDOW", lambda: self.thread_it(self.close_app()))
"""==================================="""
"""创建一个Tab标签"""
self.add_image_notebook()
def add_image_notebook(self):
# 创建一个Tab标签对象,并指定宽高,内容填充边距
notebook = Notebook(self._root, width=680, height=460, padding=3)
"""
添加notebook的子页面:Frame
参数:
bg:frame背景颜色
width:宽度
height:高度
"""
frame = Frame(notebook, bg='#f8f7f6', width=680, height=460)
notebook.add(child=frame, text='图片格式转换')
"""
grid网格布局: 使用网格化布局。这里需要注意的是布局方式不能混用,不能既使用grid网格布局又实用pack布局
参数:
row:放在第x行,从0开始
column: 放在第x列,从0开始
rowspan: 合并行单元格
columnspan: 合并列单元格
padx:设置水平方向的内填充距离
pady:设置垂直方向的内填充距离
ipadx: 设置水平方向的外部填充距离
ipady: 设置垂直方向的外部填充距离
"""
notebook.grid(row=0, column=0, rowspan=12, columnspan=12, padx=10, pady=5)
def thread_it(self, func, *args):
self._thread = Thread(target=func, args=args, daemon=True)
self._thread.start()
def close_app(self):
ans = askyesno(title='提示', message='是否确定退出程序?')
if ans:
# 销毁窗口
self._root.destroy()
exit()
else:
return None
if '__main__' == __name__:
tk = Tk()
main_tk = MainTk(root=tk)
# 启动窗口
tk.mainloop()
main_tk.py
# -*- coding: UTF-8 -*-
from tkinter import Tk
from tkinter import Frame
from tkinter import Radiobutton # 单选框
from tkinter import Button # 按钮
from tkinter.constants import DISABLED # 引入通用常量,禁用
from tkinter.constants import NORMAL # 引入通用常量,启用
from tkinter import StringVar # 变量
from tkinter.messagebox import askyesno
from tkinter.ttk import Notebook # Tab标签
from threading import Thread
from sys import exit
"""
首行配置输出中文乱码
引入tk依赖:最好使用from的方式引入,避免后续打包的时候会加入很多无用的包,造成运行文件过大
"""
class MainTk:
"""MainTk: 主窗口配置对象"""
_root = None
"""私有成员变量,不能通过对象点的方式获得,必须通过get方式获取"""
_thread = None
"""线程对象"""
def __init__(self, root: Tk):
self._root = root
"""设置窗口大小和位置"""
width = 700 # 窗口宽度
height = 500 # 窗口高度
position_x = 300 # 窗口距离屏幕左边的水平距离
position_y = 100 # 窗口距离屏幕右边的水平距离
geometry = str(width) + 'x' + str(height) + '+' + str(position_x) + '+' + str(position_y)
self._root.geometry(geometry)
"""设置窗口标题"""
self._root.title('Tools')
"""设置窗口图标:图标路径,我这里logo放在运行main的当前目录"""
self._root.iconbitmap('logo.ico')
"""点击右上角关闭窗口的监听"""
self._root.protocol("WM_DELETE_WINDOW", lambda: self.thread_it(self.close_app()))
"""==================================="""
"""创建一个Tab标签"""
self.add_image_notebook()
def add_image_notebook(self):
# 创建一个Tab标签对象,并指定宽高,内容填充边距
notebook = Notebook(self._root, width=680, height=460, padding=3)
"""
添加notebook的子页面:Frame
参数:
bg:frame背景颜色
width:宽度
height:高度
"""
frame = Frame(notebook, bg='#f8f7f6', width=680, height=460)
"""
在frame内添加其他组件:
Radiobutton单选框:
参数:
master:单选框的上级组件
text: 单选框显示的文本
variable: 标识为同一组单选框,不和其他类型冲突
cursor: 鼠标悬浮到单选框时的样式 hand2手型
state: 单选框状态,使用引入的常量表示,NORMAL,一般状态;DISABLED,禁用状态
Button按钮:
参数:
"""
val = StringVar(value='to_ico') # 给默认值
r_btn1 = Radiobutton(frame, text='图片转ico', value='to_ico', variable=val, cursor='hand2', state=NORMAL)
r_btn2 = Radiobutton(frame, text='图片转svg', value='to_svg', variable=val, cursor='hand2', state=DISABLED)
# 单选框添加到窗口
r_btn1.grid(row=0, column=0)
r_btn2.grid(row=0, column=1)
change_btn = Button(frame, text='选择预览')
convert_btn = Button(frame, text='生成图片')
change_btn.grid(row=0, column=2, padx=10)
convert_btn.grid(row=0, column=3, padx=10)
# frame添加到notebook
notebook.add(child=frame, text='图片格式转换')
"""
grid网格布局: 使用网格化布局。这里需要注意的是布局方式不能混用,不能既使用grid网格布局又实用pack布局
参数:
row:放在第x行,从0开始
column: 放在第x列,从0开始
rowspan: 合并行单元格
columnspan: 合并列单元格
padx:设置水平方向的外填充距离
pady:设置垂直方向的外填充距离
ipadx: 设置水平方向的内填充距离
ipady: 设置垂直方向的内填充距离
"""
notebook.grid(row=0, column=0, rowspan=12, columnspan=12, padx=10, pady=5)
def thread_it(self, func, *args):
self._thread = Thread(target=func, args=args, daemon=True)
self._thread.start()
def close_app(self):
ans = askyesno(title='提示', message='是否确定退出程序?')
if ans:
# 销毁窗口
self._root.destroy()
exit()
else:
return None
if '__main__' == __name__:
tk = Tk()
main_tk = MainTk(root=tk)
# 启动窗口
tk.mainloop()
main_tk.py
# -*- coding: UTF-8 -*-
from tkinter import Tk
from tkinter import Frame
from tkinter import Radiobutton # 单选框
from tkinter import Button # 按钮
from tkinter import Label # 文本标签,可以借助文本标签展示图片
from tkinter import filedialog # 导入文件路径选择
from tkinter.constants import DISABLED # 引入通用常量,禁用
from tkinter.constants import NORMAL # 引入通用常量,启用
from tkinter.constants import N # 引入通用常量,位置
from tkinter.constants import S # 引入通用常量,位置
from tkinter.constants import W # 引入通用常量,位置
from tkinter.constants import E # 引入通用常量,位置
from tkinter import StringVar # 变量
from tkinter.messagebox import askyesno
from tkinter.messagebox import showerror # 错误对话提示框
from tkinter.messagebox import showinfo # 消息提示对话提示框
from tkinter.ttk import Notebook # Tab标签
from threading import Thread
from PIL import Image, ImageTk # pip3 install Image
from sys import exit
"""
首行配置输出中文乱码
引入tk依赖:最好使用from的方式引入,避免后续打包的时候会加入很多无用的包,造成运行文件过大
"""
class MainTk:
"""MainTk: 主窗口配置对象"""
_root = None
"""私有成员变量,不能通过对象点的方式获得,必须通过get方式获取"""
_thread = None
"""线程对象"""
r_val = None
#
_img_lab_desc = None
_img_lab = None
# 图片本地地址
_image_file_path = None
# 设置成全局变量:子页面
frame = None
# 按钮
change_btn = None
convert_btn = None
def __init__(self, root: Tk):
self._root = root
"""设置窗口大小和位置"""
width = 700 # 窗口宽度
height = 500 # 窗口高度
position_x = 300 # 窗口距离屏幕左边的水平距离
position_y = 100 # 窗口距离屏幕右边的水平距离
geometry = str(width) + 'x' + str(height) + '+' + str(position_x) + '+' + str(position_y)
self._root.geometry(geometry)
"""设置窗口标题"""
self._root.title('Tools')
"""设置窗口图标:图标路径,我这里logo放在运行main的当前目录"""
self._root.iconbitmap('logo.ico')
"""点击右上角关闭窗口的监听"""
self._root.protocol("WM_DELETE_WINDOW", lambda: self.thread_it(self.close_app()))
"""==================================="""
"""创建一个Tab标签"""
self.add_image_notebook()
def add_image_notebook(self):
# 创建一个Tab标签对象,并指定宽高,内容填充边距
notebook = Notebook(self._root, width=680, height=460, padding=3)
"""
添加notebook的子页面:Frame
参数:
bg:frame背景颜色
width:宽度
height:高度
"""
self.frame = Frame(notebook, bg='#f8f7f6', width=680, height=460)
"""
在frame内添加其他组件:
Radiobutton单选框:
参数:
master:单选框的上级组件
text: 单选框显示的文本
variable: 标识为同一组单选框,不和其他类型冲突
cursor: 鼠标悬浮到单选框时的样式 hand2手型
state: 单选框状态,使用引入的常量表示,NORMAL,一般状态;DISABLED,禁用状态
Button按钮:
参数:
"""
self.r_val = StringVar(value='to_ico') # 给默认值
r_btn1 = Radiobutton(self.frame, text='图片转ico', value='to_ico', variable=self.r_val, cursor='hand2',
state=NORMAL)
r_btn2 = Radiobutton(self.frame, text='图片转svg', value='to_svg', variable=self.r_val, cursor='hand2',
state=DISABLED)
# 单选框添加到窗口
r_btn1.grid(row=0, column=0)
r_btn2.grid(row=0, column=1)
self.change_btn = Button(self.frame, text='选择预览', command=self.change_image)
self.convert_btn = Button(self.frame, text='生成图片', state=DISABLED, command=self.to_convert)
self.change_btn.grid(row=0, column=2, padx=10)
self.convert_btn.grid(row=0, column=3, padx=10)
# frame添加到notebook
notebook.add(child=self.frame, text='图片格式转换')
"""
grid网格布局: 使用网格化布局。这里需要注意的是布局方式不能混用,不能既使用grid网格布局又实用pack布局
参数:
row:放在第x行,从0开始
column: 放在第x列,从0开始
rowspan: 合并行单元格
columnspan: 合并列单元格
padx:设置水平方向的外填充距离
pady:设置垂直方向的外填充距离
ipadx: 设置水平方向的内填充距离
ipady: 设置垂直方向的内填充距离
"""
notebook.grid(row=0, column=0, rowspan=12, columnspan=12, padx=10, pady=5)
def change_image(self):
"""
选择图片进行预览
"""
# 使用文件选择框:弹出对话框,选择指定类型的文件。返回选择文件的绝对路径
d_path = filedialog.askopenfilename(title='选择图片',
filetypes=[('Image File', "*.png *.jpg *.jpeg *.svg")])
if not d_path:
return
# 选择的图片赋值给成员变量,方便转换图片的时候使用
self._image_file_path = d_path
# 获取到单选框选择的value值
val = self.r_val.get()
# 判断是否符合单选框选中的值,决定执行怎样预览图片
if val == 'to_ico' or val == 'to_svg':
self.show_image(self.frame, self._image_file_path)
else:
self.show_default(self.frame, self._image_file_path)
def show_image(self, parent_frame: Frame, file_image_path: str):
print('file_path:', file_image_path)
self._img_lab_desc = Label(parent_frame, text='图片预览')
self._img_lab_desc.grid(row=1, column=0, columnspan=24, padx=(0, 0), sticky='NSWE')
# ====================================
# https://blog.csdn.net/fjdmy001/article/details/78498150
# 如果Label展示图片不能显示出来,变量前面加一个global就能正常显示了
global img, photo_image
img = Image.open(file_image_path)
photo_image = ImageTk.PhotoImage(img.resize((180, 180)))
self._img_lab = Label(parent_frame, image=photo_image, height=300, width=250)
# ====================================
"""
sticky: 对齐方式
N:向上对齐
S:向下对齐
W:向左对齐
E:向右对齐
"""
self._img_lab.grid(row=2, column=0, columnspan=24, padx=(0, 0), pady=(5, 0), sticky='NSWE')
if file_image_path is not None or file_image_path != '':
self.convert_btn.config(state=NORMAL)
def show_default(self, parent_frame: Frame, file_image_path):
self._img_lab_desc = Label(parent_frame, text='该格式暂不支持预览,可直接转换')
self._img_lab_desc.grid(row=1, column=0, columnspan=24, padx=(0, 0), sticky=N + S + W + E)
if file_image_path is not None or file_image_path != '':
self.convert_btn.config(state=NORMAL)
def to_convert(self):
"""
去进行图片转换
"""
tip = True
# 获取需要处理的转换类型
ty = self.r_val.get()
if ty == 'to_ico':
self.image_to_ico(self._image_file_path)
elif ty == 'to_svg':
self.image_to_svg(self._image_file_path)
else:
tip = False
showerror('提示', '未处理的转换类型.')
if tip:
showinfo('提示', '转换完成')
def image_to_ico(self, file_path, icon_sizes=None):
"""
图片转换为ico
"""
# 给默认值
if icon_sizes is None:
icon_sizes = [(64, 64)]
# 处理图片名
arr = file_path.split('/')
file_name = arr[len(arr) - 1].split('.')[0]
# 打开文件提示框,选择图片生成路径
save = filedialog.asksaveasfile(title='下载路径', initialfile=file_name + '.ico',
filetypes=[("Image Types", "*.ico")])
print('dp: %s' % save.name)
# 以只读的方式打开png图片
image = Image.open(file_path, 'r')
# icon_sizes = [(16, 16), (32, 32), (48, 48), (64, 64)]
# icon_sizes = [(64, 64)]
image.save(save.name, sizes=icon_sizes)
def image_to_svg(self, file_path):
"""
图片转换为svg
"""
# 处理图片名
arr = file_path.split('/')
file_name = arr[len(arr) - 1].split('.')[0]
# 打开文件提示框,选择图片生成路径
save = filedialog.asksaveasfile(title='下载路径', initialfile=file_name + '.svg',
filetypes=[("Image Types", "*.svg")])
print('dp: %s' % save.name)
image = Image.open(file_path).convert('RGBA')
data = image.load()
width, height = image.size
out = open(save.name, "w")
out.write('\n')
out.write('<svg id="svg2" xmlns="http://www.w3.org/2000/svg" version="1.1" \
width="%(x)i" height="%(y)i" viewBox="0 0 %(x)i %(y)i">\n' % \
{'x': width, 'y': height})
for y in range(height):
for x in range(width):
rgba = data[x, y]
rgb = '#%02x%02x%02x' % rgba[:3]
if rgba[3] > 0:
out.write('<rect width="1" height="1" x="%i" y="%i" fill="%s" \
fill-opacity="%.2f" />\n' % (x, y, rgb, rgba[3] / 255.0))
out.write('\n')
out.close()
def thread_it(self, func, *args):
self._thread = Thread(target=func, args=args, daemon=True)
self._thread.start()
def close_app(self):
ans = askyesno(title='提示', message='是否确定退出程序?')
if ans:
# 销毁窗口
self._root.destroy()
exit()
else:
return None
if '__main__' == __name__:
tk = Tk()
main_tk = MainTk(root=tk)
# 启动窗口
tk.mainloop()
生成的icon图标,就可以作为python左上角的图标展示了,同时还可以用作打包exe图标。