import
tkinter as tk
from
tkinter
import
ttk, messagebox, filedialog
import
subprocess
import
json
import
os
from
pathlib
import
Path
import
win32event
import
win32process
import
win32con
import
win32api
import
sys
class
AppLauncher:
def
__init__(
self
):
self
.root
=
tk.Tk()
self
.root.title(
"应用多开启动器--*制作,python学习"
)
self
.root.geometry(
"450x500"
)
icon_path
=
self
.create_program_icon()
self
.root.iconbitmap(icon_path)
self
.root.iconbitmap(icon_path)
self
.style
=
ttk.Style()
self
.style.configure(
"Custom.TFrame"
, background
=
"#f0f0ff"
)
self
.style.configure(
"Custom.TButton"
,
padding
=
5
,
background
=
"#e6e6fa"
,
font
=
(
"微软雅黑"
,
9
))
self
.style.configure(
"Custom.TLabel"
,
font
=
(
"微软雅黑"
,
9
),
foreground
=
"#483d8b"
)
self
.style.configure(
"Custom.Treeview"
,
background
=
"#ffffff"
,
fieldbackground
=
"#ffffff"
,
font
=
(
"微软雅黑"
,
9
))
self
.style.configure(
"Custom.Treeview.Heading"
,
font
=
(
"微软雅黑"
,
9
,
"bold"
),
foreground
=
"#483d8b"
)
self
.config_file
=
"app_config.json"
self
.apps
=
self
.load_config()
self
.mutex_handles
=
{}
self
.create_widgets()
def
load_config(
self
):
if
os.path.exists(
self
.config_file):
with
open
(
self
.config_file,
'r'
, encoding
=
'utf-8'
) as f:
return
json.load(f)
return
{
"微信"
: {
"path"
: r
"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe"
,
"type"
:
"wechat"
},
"QQ"
: {
"path"
: r
"C:\Program Files (x86)\Tencent\QQ\Bin\QQ.exe"
,
"type"
:
"normal"
}
}
def
save_config(
self
):
with
open
(
self
.config_file,
'w'
, encoding
=
'utf-8'
) as f:
json.dump(
self
.apps, f, ensure_ascii
=
False
, indent
=
4
)
def
create_widgets(
self
):
main_frame
=
ttk.Frame(
self
.root, padding
=
"10"
, style
=
"Custom.TFrame"
)
main_frame.grid(row
=
0
, column
=
0
, sticky
=
(tk.W, tk.E, tk.N, tk.S))
title_label
=
ttk.Label(main_frame,
text
=
"✨ 应用多开启动器 ✨"
,
style
=
"Custom.TLabel"
,
font
=
(
"微软雅黑"
,
14
,
"bold"
))
title_label.grid(row
=
0
, column
=
0
, columnspan
=
2
, pady
=
(
0
,
10
))
ttk.Label(main_frame, text
=
"📱 已配置的应用:"
, style
=
"Custom.TLabel"
).grid(row
=
1
, column
=
0
, sticky
=
tk.W)
self
.tree
=
ttk.Treeview(main_frame, columns
=
(
'路径'
,), height
=
10
, style
=
"Custom.Treeview"
)
self
.tree.heading(
'#0'
, text
=
'📋 应用名称'
)
self
.tree.heading(
'路径'
, text
=
'📂 应用路径'
)
self
.tree.grid(row
=
2
, column
=
0
, columnspan
=
2
, sticky
=
(tk.W, tk.E))
self
.tree.bind(
'<Double-1>'
,
self
.on_double_click)
self
.update_app_list()
category_frame
=
ttk.Frame(main_frame, style
=
"Custom.TFrame"
)
category_frame.grid(row
=
0
, column
=
0
, columnspan
=
2
, sticky
=
(tk.W, tk.E), pady
=
(
0
,
5
))
ttk.Label(category_frame, text
=
"📑 分类:"
, style
=
"Custom.TLabel"
).pack(side
=
tk.LEFT)
categories
=
[
"全部"
,
"聊天工具"
,
"办公软件"
,
"游戏"
,
"其他"
]
self
.category_var
=
tk.StringVar(value
=
"全部"
)
for
cat
in
categories:
ttk.Radiobutton(category_frame,
text
=
cat,
value
=
cat,
variable
=
self
.category_var,
command
=
self
.filter_by_category,
style
=
"Custom.TRadiobutton"
).pack(side
=
tk.LEFT, padx
=
5
)
name_frame
=
ttk.Frame(main_frame, style
=
"Custom.TFrame"
)
name_frame.grid(row
=
3
, column
=
0
, columnspan
=
2
, sticky
=
(tk.W, tk.E), pady
=
(
10
,
0
))
ttk.Label(name_frame, text
=
"✏️ 应用名称:"
, style
=
"Custom.TLabel"
).pack(side
=
tk.LEFT)
self
.app_name
=
ttk.Entry(name_frame)
self
.app_name.pack(side
=
tk.LEFT, fill
=
tk.X, expand
=
True
, padx
=
(
5
,
0
))
path_frame
=
ttk.Frame(main_frame, style
=
"Custom.TFrame"
)
path_frame.grid(row
=
4
, column
=
0
, columnspan
=
2
, sticky
=
(tk.W, tk.E))
ttk.Label(path_frame, text
=
"📁 应用路径:"
, style
=
"Custom.TLabel"
).pack(side
=
tk.LEFT)
self
.app_path
=
ttk.Entry(path_frame)
self
.app_path.pack(side
=
tk.LEFT, fill
=
tk.X, expand
=
True
, padx
=
(
5
,
0
))
ttk.Button(path_frame,
text
=
"浏览"
,
command
=
self
.browse_file,
style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
(
5
,
0
))
button_frame
=
ttk.Frame(main_frame, style
=
"Custom.TFrame"
)
button_frame.grid(row
=
5
, column
=
0
, columnspan
=
2
, pady
=
10
)
ttk.Button(button_frame, text
=
"➕ 添加应用"
, command
=
self
.add_app, style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(button_frame, text
=
"✏️ 编辑应用"
, command
=
self
.edit_app, style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(button_frame, text
=
"❌ 删除应用"
, command
=
self
.remove_app, style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(button_frame, text
=
"▶️ 启动实例"
, command
=
self
.launch_instance, style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
5
)
self
.root.configure(bg
=
"#f0f0ff"
)
status_frame
=
ttk.Frame(
self
.root, style
=
"Custom.TFrame"
)
status_frame.grid(row
=
1
, column
=
0
, sticky
=
(tk.W, tk.E))
ttk.Label(status_frame,
text
=
"💡 提示:双击应用可以快速编辑"
,
style
=
"Custom.TLabel"
).pack(side
=
tk.LEFT, padx
=
10
)
self
.context_menu
=
tk.Menu(
self
.root, tearoff
=
0
)
self
.context_menu.add_command(label
=
"✏️ 编辑"
, command
=
self
.edit_app)
self
.context_menu.add_command(label
=
"❌ 删除"
, command
=
self
.remove_app)
self
.context_menu.add_separator()
self
.context_menu.add_command(label
=
"▶️ 启动"
, command
=
self
.launch_instance)
self
.tree.bind(
"<Button-3>"
,
self
.show_context_menu)
search_frame
=
ttk.Frame(main_frame, style
=
"Custom.TFrame"
)
search_frame.grid(row
=
1
, column
=
0
, columnspan
=
2
, sticky
=
(tk.W, tk.E), pady
=
(
0
,
5
))
ttk.Label(search_frame, text
=
"🔍 搜索:"
, style
=
"Custom.TLabel"
).pack(side
=
tk.LEFT)
self
.search_var
=
tk.StringVar()
self
.search_var.trace(
'w'
,
self
.filter_apps)
search_entry
=
ttk.Entry(search_frame, textvariable
=
self
.search_var)
search_entry.pack(side
=
tk.LEFT, fill
=
tk.X, expand
=
True
, padx
=
(
5
,
0
))
def
update_app_list(
self
):
for
item
in
self
.tree.get_children():
self
.tree.delete(item)
for
app_name, app_info
in
self
.apps.items():
path
=
app_info[
"path"
]
if
isinstance
(app_info,
dict
)
else
app_info
self
.tree.insert('
', '
end', text
=
app_name, values
=
(path,))
def
add_app(
self
):
name
=
self
.app_name.get().strip()
path
=
self
.app_path.get().strip()
if
not
name
or
not
path:
messagebox.showerror(
"错误"
,
"应用名称和路径不能为空!"
)
return
if
not
os.path.exists(path):
messagebox.showerror(
"错误"
,
"应用路径不存在!"
)
return
if
"WeChat.exe"
in
path:
self
.apps[name]
=
{
"path"
: path,
"type"
:
"wechat"
}
else
:
self
.apps[name]
=
{
"path"
: path,
"type"
:
"normal"
}
self
.save_config()
self
.update_app_list()
self
.app_name.delete(
0
, tk.END)
self
.app_path.delete(
0
, tk.END)
def
remove_app(
self
):
selected
=
self
.tree.selection()
if
not
selected:
messagebox.showerror(
"错误"
,
"请先选择要删除的应用!"
)
return
app_name
=
self
.tree.item(selected[
0
])[
'text'
]
if
app_name
in
self
.apps:
del
self
.apps[app_name]
self
.save_config()
self
.update_app_list()
def
launch_instance(
self
):
selected
=
self
.tree.selection()
if
not
selected:
messagebox.showerror(
"错误"
,
"请先选择要启动的应用!"
)
return
app_name
=
self
.tree.item(selected[
0
])[
'text'
]
app_info
=
self
.apps[app_name]
if
isinstance
(app_info,
str
):
app_path
=
app_info
app_type
=
"normal"
else
:
app_path
=
app_info[
"path"
]
app_type
=
app_info[
"type"
]
try
:
if
app_type
=
=
"wechat"
:
self
.launch_wechat(app_path)
else
:
subprocess.Popen([app_path])
messagebox.showinfo(
"成功"
, f
"已启动 {app_name} 的新实例!"
)
except
Exception as e:
messagebox.showerror(
"错误"
, f
"启动失败:{str(e)}"
)
def
launch_wechat(
self
, wechat_path):
mutex_prefix
=
"_WeChat_App_Instance_Identity_Mutex_Name"
instance_id
=
len
(
self
.mutex_handles)
+
1
mutex_name
=
f
"{mutex_prefix}_{instance_id}"
try
:
mutex
=
win32event.CreateMutex(
None
,
False
, mutex_name)
self
.mutex_handles[instance_id]
=
mutex
si
=
win32process.STARTUPINFO()
si.dwFlags
=
win32process.STARTF_USESHOWWINDOW
si.wShowWindow
=
win32con.SW_NORMAL
command
=
f
'"{wechat_path}" "-mutex:{mutex_name}"'
process_info
=
win32process.CreateProcess(
None
,
command,
None
,
None
,
False
,
win32process.CREATE_NEW_CONSOLE,
None
,
None
,
si
)
except
Exception as e:
if
mutex
in
self
.mutex_handles.values():
win32api.CloseHandle(mutex)
raise
Exception(f
"启动微信失败: {str(e)}"
)
def
on_double_click(
self
, event):
self
.edit_app()
def
edit_app(
self
):
selected
=
self
.tree.selection()
if
not
selected:
messagebox.showerror(
"错误"
,
"请先选择要编辑的应用!"
)
return
app_name
=
self
.tree.item(selected[
0
])[
'text'
]
app_info
=
self
.apps[app_name]
edit_window
=
tk.Toplevel(
self
.root)
edit_window.title(
"编辑应用"
)
edit_window.geometry(
"400x150"
)
edit_window.resizable(
False
,
False
)
edit_window.transient(
self
.root)
edit_window.grab_set()
edit_window.configure(bg
=
"#f0f0ff"
)
edit_frame
=
ttk.Frame(edit_window, padding
=
"10"
, style
=
"Custom.TFrame"
)
edit_frame.grid(row
=
0
, column
=
0
, sticky
=
(tk.W, tk.E, tk.N, tk.S))
ttk.Label(edit_frame, text
=
"应用名称:"
).grid(row
=
0
, column
=
0
, sticky
=
tk.W)
name_entry
=
ttk.Entry(edit_frame)
name_entry.insert(
0
, app_name)
name_entry.grid(row
=
0
, column
=
1
, sticky
=
(tk.W, tk.E), padx
=
5
, pady
=
5
)
ttk.Label(edit_frame, text
=
"应用路径:"
).grid(row
=
1
, column
=
0
, sticky
=
tk.W)
path_entry
=
ttk.Entry(edit_frame)
path_entry.insert(
0
, app_info[
"path"
]
if
isinstance
(app_info,
dict
)
else
app_info)
path_entry.grid(row
=
1
, column
=
1
, sticky
=
(tk.W, tk.E), padx
=
5
, pady
=
5
)
def
save_changes():
new_name
=
name_entry.get().strip()
new_path
=
path_entry.get().strip()
if
not
new_name
or
not
new_path:
messagebox.showerror(
"错误"
,
"应用名称和路径不能为空!"
, parent
=
edit_window)
return
if
not
os.path.exists(new_path):
messagebox.showerror(
"错误"
,
"应用路径不存在!"
, parent
=
edit_window)
return
if
new_name !
=
app_name:
del
self
.apps[app_name]
if
"WeChat.exe"
in
new_path:
self
.apps[new_name]
=
{
"path"
: new_path,
"type"
:
"wechat"
}
else
:
self
.apps[new_name]
=
{
"path"
: new_path,
"type"
:
"normal"
}
self
.save_config()
self
.update_app_list()
edit_window.destroy()
button_frame
=
ttk.Frame(edit_frame)
button_frame.grid(row
=
2
, column
=
0
, columnspan
=
2
, pady
=
10
)
ttk.Button(button_frame,
text
=
"💾 保存"
,
command
=
save_changes,
style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(button_frame,
text
=
"❌ 取消"
,
command
=
edit_window.destroy,
style
=
"Custom.TButton"
).pack(side
=
tk.LEFT, padx
=
5
)
edit_frame.columnconfigure(
1
, weight
=
1
)
def
__del__(
self
):
for
mutex
in
self
.mutex_handles.values():
try
:
win32api.CloseHandle(mutex)
except
:
pass
def
run(
self
):
self
.root.mainloop()
def
create_program_icon(
self
):
icon_data
=
import
base64
import
tempfile
icon_data_decoded
=
base64.b64decode(icon_data)
temp_file
=
tempfile.NamedTemporaryFile(suffix
=
'.ico'
, delete
=
False
)
temp_file.write(icon_data_decoded)
temp_file.close()
return
temp_file.name
def
browse_file(
self
):
filename
=
filedialog.askopenfilename(
title
=
"选择应用程序"
,
filetypes
=
[(
"可执行文件"
,
"*.exe"
), (
"所有文件"
,
"*.*"
)]
)
if
filename:
self
.app_path.delete(
0
, tk.END)
self
.app_path.insert(
0
, filename)
def
show_context_menu(
self
, event):
if
self
.tree.selection():
self
.context_menu.post(event.x_root, event.y_root)
def
filter_apps(
self
,
*
args):
search_text
=
self
.search_var.get().lower()
self
.tree.delete(
*
self
.tree.get_children())
for
app_name, app_info
in
self
.apps.items():
if
search_text
in
app_name.lower():
path
=
app_info[
"path"
]
if
isinstance
(app_info,
dict
)
else
app_info
self
.tree.insert('
', '
end', text
=
app_name, values
=
(path,))
def
filter_by_category(
self
):
pass
if
__name__
=
=
"__main__"
:
app
=
AppLauncher()
app.run()