import
os
import
sys
import
tkinter as tk
from
tkinter
import
ttk
import
win32ui
import
win32gui
from
PIL
import
Image, ImageTk
import
subprocess
from
win32com.client
import
Dispatch
def
get_current_path():
try
:
if
getattr
(sys,
'frozen'
,
False
):
return
os.path.dirname(sys.executable)
return
os.path.dirname(os.path.abspath(__file__))
except
Exception as e:
print
(f
"路径获取失败: {e}"
)
return
os.getcwd()
def
get_exe_icon(exe_path):
try
:
large, small
=
win32gui.ExtractIconEx(exe_path,
0
)
win32gui.DestroyIcon(small[
0
])
hdc
=
win32ui.CreateDCFromHandle(win32gui.GetDC(
0
))
hbmp
=
win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc,
32
,
32
)
hdc
=
hdc.CreateCompatibleDC()
hdc.SelectObject(hbmp)
hdc.DrawIcon((
0
,
0
), large[
0
])
bmpinfo
=
hbmp.GetInfo()
bmpstr
=
hbmp.GetBitmapBits(
True
)
img
=
Image.frombuffer(
'RGBA'
,
(bmpinfo[
'bmWidth'
], bmpinfo[
'bmHeight'
]),
bmpstr,
'raw'
,
'BGRA'
,
0
,
1
)
return
img
except
Exception as e:
print
(f
"提取图标失败: {e}"
)
return
None
class
UniformButton(tk.Frame):
def
__init__(
self
, parent, text, image, command):
super
().__init__(parent, bg
=
"white"
, highlightthickness
=
0
)
self
.button_width
=
70
self
.button_height
=
90
self
.max_chars
=
10
self
.max_lines
=
2
self
.config(width
=
self
.button_width, height
=
self
.button_height)
self
.pack_propagate(
False
)
self
.content_frame
=
tk.Frame(
self
, bg
=
"white"
)
self
.content_frame.pack(fill
=
tk.BOTH, expand
=
True
)
self
.icon_label
=
tk.Label(
self
.content_frame,
image
=
image,
bg
=
"white"
,
borderwidth
=
0
)
self
.icon_label.image
=
image
self
.icon_label.pack(pady
=
(
5
,
2
), expand
=
True
)
processed_text
=
self
.process_text(text)
self
.text_label
=
tk.Label(
self
.content_frame,
text
=
processed_text,
wraplength
=
self
.button_width
-
15
,
justify
=
"center"
,
font
=
(
"微软雅黑"
,
8
),
bg
=
"white"
,
borderwidth
=
0
)
self
.text_label.pack(pady
=
(
0
,
5
))
self
.bind_all_children(
"<Button-1>"
,
lambda
e: command())
self
.bind(
"<Enter>"
,
self
.on_enter)
self
.bind(
"<Leave>"
,
self
.on_leave)
def
bind_all_children(
self
, event, handler):
for
child
in
self
.winfo_children():
child.bind(event, handler)
if
isinstance
(child, tk.Frame):
for
subchild
in
child.winfo_children():
subchild.bind(event, handler)
def
process_text(
self
, text):
text
=
text[:
20
]
words
=
text.split()
lines
=
[]
current_line
=
[]
for
word
in
words:
if
len
(word) >
self
.max_chars:
chunks
=
[word[i:i
+
self
.max_chars]
for
i
in
range
(
0
,
len
(word),
self
.max_chars)]
current_line.extend(chunks)
else
:
current_line.append(word)
if
sum
(
len
(w)
for
w
in
current_line)
+
(
len
(current_line)
-
1
) >
self
.max_chars:
lines.append(
" "
.join(current_line[:
-
1
]))
current_line
=
[current_line[
-
1
]]
if
len
(lines) >
=
self
.max_lines:
break
if
current_line
and
len
(lines) <
self
.max_lines:
lines.append(
" "
.join(current_line))
return
"\n"
.join(lines[:
self
.max_lines])
+
(
"..."
if
len
(text) >
20
else
"")
def
on_enter(
self
, event):
self
.config(bg
=
"#e0e0e0"
)
self
.content_frame.config(bg
=
"#e0e0e0"
)
for
child
in
self
.content_frame.winfo_children():
child.config(bg
=
"#e0e0e0"
)
def
on_leave(
self
, event):
self
.config(bg
=
"white"
)
self
.content_frame.config(bg
=
"white"
)
for
child
in
self
.content_frame.winfo_children():
child.config(bg
=
"white"
)
class
AppLauncher:
def
__init__(
self
, root):
self
.root
=
root
self
.root.title(
"便携工具箱 by:姬御风"
)
self
.root.geometry(
"530x400"
)
self
.root.resizable(
False
,
False
)
self
.icon_size
=
32
self
.set_window_icon()
self
.main_frame
=
ttk.Frame(root)
self
.main_frame.pack(fill
=
tk.BOTH, expand
=
True
, padx
=
5
, pady
=
5
)
self
.default_icon
=
self
.get_default_icon()
self
.create_category_panel()
self
.create_program_panel()
self
.current_dir
=
get_current_path()
self
.scan_directory()
self
.populate_categories()
self
.show_category(
"所有程序"
)
def
set_window_icon(
self
):
try
:
exe_path
=
sys.executable
icon_img
=
get_exe_icon(exe_path)
if
icon_img:
icon_img.save(
'temp_icon.ico'
,
'ICO'
)
self
.root.iconbitmap(
'temp_icon.ico'
)
os.remove(
'temp_icon.ico'
)
else
:
print
(
"无法提取exe图标,使用默认图标"
)
self
.root.iconbitmap(
'icon/app.ico'
)
except
Exception as e:
print
(f
"设置窗口图标失败: {e}"
)
def
resolve_shortcut(
self
, path):
try
:
shell
=
Dispatch(
'WScript.Shell'
)
shortcut
=
shell.CreateShortCut(path)
return
shortcut.TargetPath
except
Exception as _e:
print
(f
"快捷方式解析失败: {path}"
)
return
None
def
get_program_icon(
self
, path):
try
:
if
path.lower().endswith(
'.lnk'
):
target
=
self
.resolve_shortcut(path)
if
target
and
os.path.exists(target):
path
=
target
large, _
=
win32gui.ExtractIconEx(path,
0
)
if
not
large:
raise
Exception(
"No icons found"
)
hicon
=
large[
0
]
hdc
=
win32ui.CreateDCFromHandle(win32gui.GetDC(
0
))
hbmp
=
win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc,
self
.icon_size,
self
.icon_size)
hdc
=
hdc.CreateCompatibleDC()
hdc.SelectObject(hbmp)
hdc.DrawIcon((
0
,
0
), hicon)
bmpstr
=
hbmp.GetBitmapBits(
True
)
img
=
Image.frombuffer(
'RGBA'
, (
self
.icon_size,
self
.icon_size),
bmpstr,
'raw'
,
'BGRA'
,
0
,
1
)
return
ImageTk.PhotoImage(img)
except
Exception as _e:
print
(f
"图标加载失败: {os.path.basename(path)}"
)
return
self
.default_icon
def
get_default_icon(
self
):
try
:
shell32
=
"C:\\Windows\\System32\\shell32.dll"
large, _
=
win32gui.ExtractIconEx(shell32,
3
)
hicon
=
large[
0
]
hdc
=
win32ui.CreateDCFromHandle(win32gui.GetDC(
0
))
hbmp
=
win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc,
self
.icon_size,
self
.icon_size)
hdc
=
hdc.CreateCompatibleDC()
hdc.SelectObject(hbmp)
hdc.DrawIcon((
0
,
0
), hicon)
bmpstr
=
hbmp.GetBitmapBits(
True
)
img
=
Image.frombuffer(
'RGBA'
, (
self
.icon_size,
self
.icon_size),
bmpstr,
'raw'
,
'BGRA'
,
0
,
1
)
return
ImageTk.PhotoImage(img)
except
Exception as _e:
print
(f
"默认图标加载失败: {_e}"
)
return
ImageTk.PhotoImage(Image.new(
'RGBA'
, (
self
.icon_size,
self
.icon_size), (
240
,
240
,
240
)))
def
create_category_panel(
self
):
self
.category_frame
=
ttk.Frame(
self
.main_frame, width
=
99
)
self
.category_frame.pack(side
=
tk.LEFT, fill
=
tk.Y, padx
=
3
)
self
.category_canvas
=
tk.Canvas(
self
.category_frame,
width
=
99
,
highlightthickness
=
0
)
scrollbar
=
ttk.Scrollbar(
self
.category_frame,
orient
=
"vertical"
,
command
=
self
.category_canvas.yview)
self
.category_container
=
ttk.Frame(
self
.category_canvas)
self
.category_container.bind(
"<Configure>"
,
lambda
e:
self
.category_canvas.configure(
scrollregion
=
self
.category_canvas.bbox(
"all"
),
width
=
99
))
self
.category_canvas.create_window((
0
,
0
),
window
=
self
.category_container,
anchor
=
"nw"
,
width
=
99
)
self
.category_canvas.configure(yscrollcommand
=
scrollbar.
set
)
self
.category_canvas.pack(side
=
tk.LEFT, fill
=
tk.BOTH, expand
=
True
)
scrollbar.pack(side
=
tk.RIGHT, fill
=
tk.Y)
ttk.Label(
self
.category_container,
text
=
"应用分类"
,
font
=
(
"微软雅黑"
,
9
),
padding
=
3
).pack(pady
=
5
)
def
create_program_panel(
self
):
self
.program_frame
=
ttk.Frame(
self
.main_frame)
self
.program_frame.pack(side
=
tk.RIGHT, fill
=
tk.BOTH, expand
=
True
)
self
.program_canvas
=
tk.Canvas(
self
.program_frame,
highlightthickness
=
0
)
scrollbar
=
ttk.Scrollbar(
self
.program_frame,
orient
=
"vertical"
,
command
=
self
.program_canvas.yview)
self
.program_container
=
ttk.Frame(
self
.program_canvas)
self
.program_container.bind(
"<Configure>"
,
lambda
e:
self
.program_canvas.configure(
scrollregion
=
self
.program_canvas.bbox(
"all"
)
))
self
.program_canvas.create_window((
0
,
0
),
window
=
self
.program_container,
anchor
=
"nw"
)
self
.program_canvas.configure(yscrollcommand
=
scrollbar.
set
)
self
.program_canvas.pack(side
=
tk.LEFT, fill
=
tk.BOTH, expand
=
True
)
scrollbar.pack(side
=
tk.RIGHT, fill
=
tk.Y)
def
scan_directory(
self
):
self
.categories
=
{
"所有程序"
: []}
executable_ext
=
[
'.exe'
,
'.bat'
,
'.cmd'
,
'.lnk'
]
for
item
in
os.listdir(
self
.current_dir):
path
=
os.path.join(
self
.current_dir, item)
if
os.path.isdir(path):
self
.categories[item]
=
[]
for
root, _, files
in
os.walk(path):
for
file
in
files:
if
os.path.splitext(
file
)[
1
].lower()
in
executable_ext:
full_path
=
os.path.join(root,
file
)
self
.categories[item].append({
"name"
: os.path.splitext(
file
)[
0
],
"path"
: full_path
})
self
.categories[
"所有程序"
].append({
"name"
: os.path.splitext(
file
)[
0
],
"path"
: full_path
})
else
:
if
os.path.splitext(item)[
1
].lower()
in
executable_ext:
self
.categories[
"所有程序"
].append({
"name"
: os.path.splitext(item)[
0
],
"path"
: path
})
def
populate_categories(
self
):
for
category
in
self
.categories.keys():
btn
=
ttk.Button(
self
.category_container,
text
=
category,
command
=
lambda
c
=
category:
self
.show_category(c),
width
=
12
)
btn.pack(fill
=
tk.X, padx
=
2
, pady
=
2
)
def
show_category(
self
, category):
for
widget
in
self
.program_container.winfo_children():
widget.destroy()
programs
=
self
.categories.get(category, [])
if
not
programs:
ttk.Label(
self
.program_container, text
=
"该分类暂无应用"
).pack(pady
=
50
)
return
max_columns
=
5
row
=
col
=
0
for
idx, program
in
enumerate
(programs):
col
=
idx
%
max_columns
row
=
idx
/
/
max_columns
button
=
UniformButton(
parent
=
self
.program_container,
text
=
program[
"name"
],
image
=
self
.get_program_icon(program[
"path"
]),
command
=
lambda
p
=
program[
"path"
]:
self
.launch_program(p)
)
button.grid(row
=
row, column
=
col, padx
=
3
, pady
=
3
, sticky
=
"nsew"
)
self
.program_container.grid_columnconfigure(col, weight
=
1
)
self
.program_container.grid_rowconfigure(row, weight
=
1
)
def
launch_program(
self
, path):
try
:
subprocess.Popen(f
'"{path}"'
, shell
=
True
)
except
Exception as _e:
print
(f
"启动失败: {_e}"
)
if
__name__
=
=
"__main__"
:
root_window
=
tk.Tk()
style
=
ttk.Style()
style.configure(
"TButton"
,
padding
=
3
,
font
=
(
"微软雅黑"
,
8
),
width
=
12
)
style.
map
(
"TButton"
,
background
=
[(
'active'
,
'#f0f0f0'
)])
AppLauncher(root_window)
root_window.mainloop()