Skip to content

Commit 3faaa6a

Browse files
committed
update
1 parent b96aef4 commit 3faaa6a

File tree

6 files changed

+373
-2
lines changed

6 files changed

+373
-2
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
# PyInstallerGUI
2-
PyInstallerGUI
1+
# GUI应用生成器
2+
3+
一个 PyInstaller 的 GUI 实现,使用 Python 语言开发,GUI 基于 Tkinter,利用 PyInstaller 将 Python 脚本语言打包发布成单个的可执行程序
4+
5+
预览图
6+
7+
![WX20201114-205346@2x](screenshots/WX20201114-205346@2x.png)

build.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# _*_ coding:utf-8 _*_
2+
3+
cd src
4+
pyinstaller \
5+
--clean\
6+
--noconfirm\
7+
-w\
8+
--distpath=../dist\
9+
--workpath=../\
10+
build.spec
11+
cd ..
122 KB
Loading

src/app.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from os import system
4+
from threading import Thread
5+
from tkinter import Tk
6+
# from shlex import split
7+
# from subprocess import call
8+
9+
from utils import set_window_center
10+
from view import View
11+
# import PyInstaller
12+
# import PyInstaller.__main__
13+
14+
class App(View):
15+
def __init__(self, master=None):
16+
super(App, self).__init__()
17+
18+
def fn_build(self):
19+
'''生成可执行文件'''
20+
if not self.status_build:
21+
thread_build = Thread(target=self.fn_thread)
22+
thread_build.setDaemon(True)
23+
thread_build.start()
24+
else:
25+
self.label_status['text'] = '正在打包,请稍后再操作!'
26+
27+
def fn_thread(self):
28+
'''线程执行生成动作'''
29+
if len(self.entry_value_list[0].get()) == 0:
30+
self.label_status['text'] = '请选择源文件'
31+
return
32+
self.status_build = True
33+
cmd = self.fn_build_cmd()
34+
print(cmd)
35+
self.label_status['text'] = '正在打包,请稍等。。。'
36+
try:
37+
# PyInstaller.__main__.run(cmd)
38+
system(' '.join(cmd))
39+
# call(split(' '.join(cmd)), shell=True)
40+
self.status_build = False
41+
self.label_status['text'] = '打包成功!'
42+
except Exception as e:
43+
self.label_status['text'] = str(e)
44+
self.status_build = False
45+
46+
if __name__ == '__main__':
47+
root = Tk()
48+
root.title('GUI应用生成器')
49+
App(root)
50+
set_window_center(root)
51+
root.mainloop()

src/utils.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# _*_ coding:utf-8 _*_
2+
# utils functions
3+
4+
5+
def set_window_center(window, width=None, height=None, minsize=True, resize=False):
6+
"""设置窗口宽高及居中"""
7+
# 获取窗口宽高
8+
if width == None or height == None:
9+
# 宽高为 None 时取窗口自身大小
10+
window.update_idletasks() # 更新
11+
window.withdraw() # 隐藏重绘
12+
# window.update() # 获取窗口宽高之前需要先刷新窗口
13+
if width is None:
14+
width = window.winfo_width()
15+
if height is None:
16+
height = window.winfo_height()
17+
18+
# 获取屏幕宽高
19+
w_s = window.winfo_screenwidth()
20+
h_s = window.winfo_screenheight()
21+
22+
# 计算 x, y 位置
23+
x_co = (w_s - width) / 2
24+
y_co = (h_s - height) / 2
25+
26+
# 设置窗口宽高和居中定位
27+
window.geometry("%dx%d+%d+%d" % (width, height, x_co, y_co))
28+
window.deiconify() # 显示
29+
# 是否设置窗口最小尺寸
30+
if minsize:
31+
window.minsize(width, height)
32+
# 是否可调整大小
33+
if resize:
34+
window.resizable(True, True)
35+
else:
36+
window.resizable(False, False)
37+
38+
39+
def get_screen_size(window):
40+
"""获取屏幕 宽、高"""
41+
return window.winfo_screenwidth(), window.winfo_screenheight()
42+
43+
44+
def get_window_size(window):
45+
"""获取窗口 宽、高"""
46+
window.update()
47+
return window.winfo_width(), window.winfo_height()
48+
49+
50+
def treeview_sort_column(tv, col, reverse):
51+
"""Treeview、列名、排列方式"""
52+
l = [(tv.set(k, col), k) for k in tv.get_children('')]
53+
# print(tv.get_children(''))
54+
l.sort(reverse=reverse) # 排序方式
55+
# rearrange items in sorted positions
56+
for index, (val, k) in enumerate(l): # 根据排序后索引移动
57+
tv.move(k, '', index)
58+
# print(k)
59+
tv.heading(col, command=lambda: treeview_sort_column(
60+
tv, col, not reverse)) # 重写标题,使之成为再点倒序的标题

src/view.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os.path
4+
from tkinter import (Button, Checkbutton, Entry, IntVar, Label, LabelFrame, StringVar, filedialog)
5+
6+
7+
class View(object):
8+
def __init__(self, master=None):
9+
self.root = master
10+
self.status_build = False
11+
self.init_view()
12+
13+
def init_view(self):
14+
'''基本框架'''
15+
self.frm_project = LabelFrame(self.root, text='项目')
16+
self.frm_config = LabelFrame(self.root, text='配置')
17+
self.frm_operate = LabelFrame(self.root, text='操作')
18+
self.frm_status = LabelFrame(self.root, text='状态')
19+
20+
self.frm_project.pack(expand='yes', side='top',
21+
fill='both', padx=15, pady=10)
22+
self.frm_config.pack(fill='x', padx=15, pady=10)
23+
self.frm_operate.pack(fill='x', padx=15, pady=10)
24+
self.frm_status.pack(side='bottom', fill='x', padx=15, pady=10)
25+
26+
self.init_project()
27+
self.init_config()
28+
self.init_operate()
29+
self.init_status()
30+
31+
def init_project(self):
32+
'''项目配置'''
33+
labels = ['入口文件:', '工作目录:', '目标路径:', '图标路径:']
34+
35+
self.entry_value_list = list()
36+
for index, label_text in enumerate(labels):
37+
temp_strvar = StringVar()
38+
temp_label = Label(self.frm_project, text=label_text)
39+
temp_entry = Entry(
40+
self.frm_project, textvariable=temp_strvar, width=50)
41+
self.entry_value_list.append(temp_strvar)
42+
temp_label.grid(row=index % 4, column=0,
43+
padx=5, pady=5, sticky='w')
44+
temp_entry.grid(row=index % 4, column=1,
45+
padx=5, pady=5, sticky='we')
46+
47+
self.btn_main_path = Button(
48+
self.frm_project, text='选择文件', command=self.fn_select_main
49+
)
50+
self.btn_work_path = Button(
51+
self.frm_project, text='选择路径', command=self.fn_work_path
52+
)
53+
self.btn_dist_path = Button(
54+
self.frm_project, text='选择路径', command=self.fn_dist_path
55+
)
56+
self.btn_ico_path = Button(
57+
self.frm_project, text='选择图标', command=self.fn_icon_path
58+
)
59+
self.btn_main_path.grid(row=0, column=2, padx=5, pady=5, sticky='we')
60+
self.btn_work_path.grid(row=1, column=2, padx=5, pady=5, sticky='w')
61+
self.btn_dist_path.grid(row=2, column=2, padx=5, pady=5, sticky='e')
62+
self.btn_ico_path.grid(row=3, column=2, padx=5, pady=5, sticky='e')
63+
64+
def init_config(self):
65+
'''配置选项'''
66+
# 定义变量,并初始化
67+
self.cfg_onefile = IntVar(value=1)
68+
self.cfg_onedir = IntVar(value=0)
69+
self.cfg_noconsole = IntVar(value=1)
70+
self.cfg_clean = IntVar(value=1)
71+
self.cfg_rename = IntVar()
72+
self.cfg_exe_name = StringVar()
73+
# 子配置框架
74+
self.frm_config_base = LabelFrame(
75+
self.frm_config, text='基本', borderwidth=0)
76+
self.frm_config_base.pack(fill='x', padx=10, pady=5, ipady=5)
77+
self.frm_config_exe = LabelFrame(
78+
self.frm_config, text='生成执行文件类型', borderwidth=0)
79+
self.frm_config_exe.pack(fill='x', padx=10, pady=5, ipady=5)
80+
self.frm_config_other = LabelFrame(
81+
self.frm_config, text='其它', borderwidth=0)
82+
self.frm_config_other.pack(fill='x', padx=10, pady=5, ipady=5)
83+
# 定义按钮
84+
self.btn_noconsole = Checkbutton(
85+
self.frm_config_base, text='关闭控制台', variable=self.cfg_noconsole)
86+
self.btn_clean = Checkbutton(
87+
self.frm_config_base, text='构建前清理', variable=self.cfg_clean)
88+
self.btn_isonefile = Checkbutton(
89+
self.frm_config_exe, text='独立执行文件', variable=self.cfg_onefile)
90+
self.btn_isonedir = Checkbutton(
91+
self.frm_config_exe, text='文件夹包含', variable=self.cfg_onedir)
92+
self.btn_rename = Checkbutton(
93+
self.frm_config_other, text='修改执行文件名', variable=self.cfg_rename)
94+
self.entry_rename = Entry(
95+
self.frm_config_other, textvariable=self.cfg_exe_name)
96+
97+
# 放置按钮
98+
self.btn_isonefile.pack(side='left', fill='x')
99+
self.btn_isonedir.pack(side='left', fill='x')
100+
self.btn_noconsole.pack(side='left', fill='x')
101+
self.btn_clean.pack(side='left', fill='x')
102+
self.btn_rename.pack(side='left', fill='x')
103+
self.entry_rename.pack(fill='x')
104+
105+
# 变量自动切换操作
106+
self.cfg_onefile.trace('w', self.cfg_onefile_trace)
107+
self.cfg_onedir.trace('w', self.cfg_onedir_trace)
108+
109+
def cfg_onefile_trace(self, *args):
110+
'''cfg_onefile 与 cfg_onedir 可以同时不选,但不能同时选中,选中独立执行文件时不能选中文件夹包'''
111+
if self.cfg_onefile.get() == 1:
112+
self.cfg_onedir.set(0)
113+
114+
def cfg_onedir_trace(self, *args):
115+
'''cfg_onefile 与 cfg_onedir 可以同时不选,但不能同时选中,选中文件夹包含时不能选中独立执行文件'''
116+
if self.cfg_onedir.get() == 1:
117+
self.cfg_onefile.set(0)
118+
119+
def init_operate(self):
120+
'''操作命令'''
121+
# 定义按钮
122+
self.btn_build = Button(
123+
self.frm_operate, text='构建生成', command=self.fn_build)
124+
self.btn_clear = Button(
125+
self.frm_operate, text='清理', command=self.fn_clear)
126+
self.btn_reset = Button(
127+
self.frm_operate, text='重置', command=self.fn_reset)
128+
# 放置按钮
129+
self.btn_build.pack(fill='x', side='left')
130+
self.btn_clear.pack(fill='x', side='left')
131+
self.btn_reset.pack(fill='x', side='left')
132+
133+
def init_status(self):
134+
'''状态栏'''
135+
self.label_status = Label(self.frm_status, text='待命')
136+
self.label_status.grid(row=1, column=0, padx=5, pady=5, sticky='we')
137+
138+
def fn_build(self):
139+
'''生成可执行文件'''
140+
pass
141+
142+
def fn_clear(self):
143+
'''清理生成文件'''
144+
pass
145+
146+
def fn_reset(self):
147+
'''重置表单内容'''
148+
for i in range(4):
149+
self.entry_value_list[i].set('')
150+
151+
self.cfg_onefile.set(1)
152+
self.cfg_noconsole.set(1)
153+
self.cfg_clean.set(1)
154+
self.cfg_rename.set(0)
155+
self.cfg_exe_name.set('')
156+
157+
def fn_select_main(self):
158+
'''选择源文件'''
159+
types = (
160+
('py files', '*.py'),
161+
('pyc files', '*.pyc'),
162+
('spec files', '*.spec'),
163+
('All files', '*.*')
164+
)
165+
path = filedialog.askopenfilename(filetypes=types)
166+
if not path:
167+
return
168+
_path = os.path.dirname(path)
169+
# 主文件
170+
self.entry_value_list[0].set(path)
171+
# 工作目录
172+
self.entry_value_list[1].set(os.path.join(_path, 'build/'))
173+
# dist目录
174+
self.entry_value_list[2].set(os.path.join(_path, 'dist/'))
175+
176+
def fn_work_path(self):
177+
'''选择工作目录'''
178+
path = filedialog.askdirectory()
179+
if not path:
180+
return
181+
self.entry_value_list[1].set(path)
182+
183+
def fn_dist_path(self):
184+
'''选择生成文件目录'''
185+
path = filedialog.askdirectory()
186+
if not path:
187+
return
188+
self.entry_value_list[2].set(path)
189+
190+
def fn_icon_path(self):
191+
'''选择图标文件'''
192+
types = (
193+
('ico files', '*.ico'),
194+
('icns files', '*.icns'),
195+
('All files', '*.*')
196+
)
197+
path = filedialog.askopenfilename(filetypes=types)
198+
if not path:
199+
return
200+
self.entry_value_list[3].set(path)
201+
202+
def fn_build_cmd(self):
203+
'''组装命令'''
204+
cmds = []
205+
cmds.append('pyinstaller')
206+
cmds.append('--windowed')
207+
# cmds.append('--filenames=build.spec')
208+
# cmds.append('/usr/local/bin/pyinstaller')
209+
210+
if self.cfg_onefile.get() == 1:
211+
cmds.append('--onefile')
212+
elif self.cfg_onedir.get() == 1:
213+
cmds.append('--onedir')
214+
215+
if self.cfg_clean.get() == 1:
216+
cmds.append('--clean')
217+
cmds.append('--noconfirm')
218+
219+
if self.cfg_noconsole.get() == 1:
220+
cmds.append('--noconsole')
221+
222+
if len(self.entry_value_list[1].get()) > 0:
223+
cmds.append('--workpath=' + self.entry_value_list[1].get())
224+
225+
if len(self.entry_value_list[2].get()) > 0:
226+
cmds.append('--distpath=' + self.entry_value_list[2].get())
227+
228+
if len(self.entry_value_list[3].get()) > 0:
229+
cmds.append('--icon=' + self.entry_value_list[3].get())
230+
231+
if self.cfg_rename.get() == 1:
232+
if len(self.cfg_exe_name.get()) > 0:
233+
cmds.append('--name=' + self.cfg_exe_name.get())
234+
235+
if len(self.entry_value_list[0].get()) > 0:
236+
cmds.append(self.entry_value_list[0].get())
237+
return cmds
238+
# return ' '.join(cmds)
239+
240+
if __name__ == '__main__':
241+
from tkinter import Tk
242+
root = Tk()
243+
View(root)
244+
root.mainloop()

0 commit comments

Comments
 (0)