Python开发Windows后台托盘应用:任务栏图标唤醒窗口问题
问题解决指南
核心问题:任务栏固定图标点击唤醒已有窗口
Windows任务栏固定逻辑依赖App User Model ID(AUMID),同时新启动的实例需要主动检测并唤醒已有进程,而非直接退出。修改方案如下:
- 完善AUMID设置,确保程序启动时尽早配置且打包前后一致
- 新实例检测到已有进程时,通过Windows API定位主窗口并发送显示指令,而非直接退出
进程残留与锁文件问题修复
原锁文件仅存储PID,无法处理进程异常崩溃场景,改用文件独占锁实现单实例:系统会在进程退出(包括异常崩溃)时自动释放锁,避免残留问题。
测试效率提升
无需每次打包--onefile:
- 使用
pyinstaller --onedir main.py生成文件夹格式打包产物,修改代码后仅替换文件夹内的main.exe即可,无需重新打包所有依赖 - 测试阶段直接运行Python脚本:创建快捷方式指向
python.exe并附加脚本路径(如python D:\path\to\main.py),将该快捷方式固定到任务栏,无需打包即可验证任务栏唤醒逻辑
完整修改代码
import os import sys import atexit import signal from pystray import Icon, Menu, MenuItem from PIL import Image, ImageDraw import threading import tkinter as tk from queue import Queue from ctypes import windll import win32gui import win32api import win32con import msvcrt LOCK_FILE = 'app.lock' exit_flag = False gui_queue = Queue() lock_file_handle = None def remove_lock_file(): """释放文件锁并删除锁文件""" global lock_file_handle if lock_file_handle: try: msvcrt.locking(lock_file_handle.fileno(), msvcrt.LK_UNLCK, 1) lock_file_handle.close() except: pass if os.path.exists(LOCK_FILE): os.remove(LOCK_FILE) def find_main_window(): """枚举窗口查找已有实例的主窗口""" def callback(handle, extra): if win32gui.GetWindowText(handle) == "Windows Tester": extra.append(handle) return True windows = [] win32gui.EnumWindows(callback, windows) return windows[0] if windows else None def is_already_running(): """检测实例是否运行,存在则唤醒窗口""" hwnd = find_main_window() if hwnd: win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) win32api.SetForegroundWindow(hwnd) return True try: global lock_file_handle lock_file_handle = open(LOCK_FILE, 'w') msvcrt.locking(lock_file_handle.fileno(), msvcrt.LK_NBLCK, 1) return False except: return True def handle_exit(signum=None, frame=None): """处理退出信号""" global exit_flag exit_flag = True remove_lock_file() # 注册退出信号处理器 signal.signal(signal.SIGINT, handle_exit) signal.signal(signal.SIGTERM, handle_exit) signal.signal(signal.SIGBREAK, handle_exit) def run_tray_icon(root): def show_window(): gui_queue.put(lambda: root.deiconify()) def hide_window(): gui_queue.put(lambda: root.withdraw()) def exit_application(icon, item): handle_exit() icon.stop() menu = Menu( MenuItem('Show', show_window), MenuItem('Hide', hide_window), MenuItem('Exit', exit_application) ) def create_image(width, height, color1, color2): image = Image.new('RGB', (width, height), color1) dc = ImageDraw.Draw(image) dc.rectangle((width//2, 0, width, height//2), fill=color2) dc.rectangle((0, height//2, width//2, height), fill=color2) return image def run_icon(): icon = Icon("Windows Tester", icon=create_image(64, 64, 'black', 'white'), menu=menu) icon.run() thread = threading.Thread(target=run_icon) thread.daemon = True thread.start() def create_main_window(): root = tk.Tk() root.title("Windows Tester") root.geometry("300x200") root.protocol("WM_DELETE_WINDOW", lambda: root.withdraw()) return root def process_gui_queue(root): """处理GUI队列任务""" while not gui_queue.empty(): action = gui_queue.get() action() if not exit_flag: root.after(100, process_gui_queue, root) else: root.quit() def start_gui_event_loop(root): process_gui_queue(root) root.mainloop() def main(): if is_already_running(): sys.exit(0) atexit.register(remove_lock_file) try: myappid = 'sirlouen.windows-testing.0.0.1' windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) except Exception as e: print(f"Failed to set App User Model ID: {e}") root = create_main_window() run_tray_icon(root) start_gui_event_loop(root) remove_lock_file() if __name__ == "__main__": main()
内容的提问来源于stack exchange,提问作者SirLouen
相关产品推荐
相关产品推荐

