1、实现思路
1、tkinter窗体使用守护线程(HotKeyThread)进行热键注册和管理
2、守护线程(HotKeyThread)需要实现
1、线程本身可控制
2、热键注册和卸载
3、热键事件监听 和对应事件相应
2、实现可停止线程(StoppableThread)
stopped_able_thread.py
import threading class StoppableThread(threading.Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): super(StoppableThread, self).__init__(group=group, target=target, name=name, args=args, kwargs=kwargs, daemon=daemon) self._stop_event = threading.Event() def stop(self): self._stop_event.set() def stopped(self): return self._stop_event.is_set()
3、win32gui, win32con,ctypes定义窗体热键行为事件
GetForegroundWindow = windll.user32.GetForegroundWindow GetWindowRect = windll.user32.GetWindowRect SetForegroundWindow = windll.user32.SetForegroundWindow GetWindowText = windll.user32.GetWindowTextA MoveWindow = windll.user32.MoveWindow EnumWindows = windll.user32.EnumWindows class RECT(Structure): _fields_ = [ ('left', c_long), ('top', c_long), ('right', c_long), ('bottom', c_long) ] class POINT(Structure): _fields_ = [ ('x', c_long), ('y', c_long), ] def getWinRect(win_hd): """ 函数功能:获取窗体的位置和大小 """ if win_hd is None: return None rect = RECT() GetWindowRect(win_hd, byref(rect)) return rect def toScreenPos(win_hd, x, y): """ 函数功能:将窗体内部坐标转换为相对于显示屏的绝对坐标 """ # 未指定窗口,则结束函数 if win_hd is None: return None rect = getWinRect(win_hd) # 指定的坐标不在窗体内,则结束函数 if x < 0 or y < 0 or x > rect.right or y > rect.bottom: return None pos = POINT() pos.x = x + rect.left pos.y = y + rect.top return pos def hide(*args, **kwargs): toggle_window(kwargs.get('title')) def toggle_window(title): hd = win32gui.FindWindow(0, title) # 根据标题找到窗口句柄 if win32gui.IsWindowVisible(hd) and win32gui.IsWindowEnabled(hd): win32gui.ShowWindow(hd, False) # 把 else: win32gui.ShowWindow(hd, True) # 把 def full_screen(title): # 查找窗口句柄 hwnd = win32gui.FindWindow(0, title) if hwnd != 0: tup = win32gui.GetWindowPlacement(hwnd) # if tup[1] == win32con.SW_SHOWMAXIMIZED: # win32gui.ShowWindow(hwnd, win32con.SW_SHOWNORMAL) # if tup[1] == win32con.SW_SHOWMINIMIZED: # pass if tup[1] == win32con.SW_SHOWNORMAL: win32gui.ShowWindow(hwnd, win32con.SW_SHOWMAXIMIZED) def restore_screen(title): hwnd = win32gui.FindWindow(0, title) if hwnd != 0: tup = win32gui.GetWindowPlacement(hwnd) if tup[1] == win32con.SW_SHOWMAXIMIZED: win32gui.ShowWindow(hwnd, win32con.SW_SHOWNORMAL) def foreground_window(title): hwnd = win32gui.FindWindow(0, title) rect = getWinRect(hwnd) if hwnd != 0: if 256 == win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE): # 置顶 win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0,0,rect.right-rect.left,rect.bottom-rect.top, win32con.SWP_NOMOVE | win32con.SWP_NOACTIVATE | win32con.SWP_NOOWNERZORDER | win32con.SWP_SHOWWINDOW) else: # 不置顶 win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0, 0, rect.right - rect.left, rect.bottom - rect.top, win32con.SWP_SHOWWINDOW | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_NOACTIVATE | win32con.SWP_NOOWNERZORDER) # win32gui.SetForegroundWindow(hwnd) # 设置前置窗口
4、HotKeyThread继承(StoppableThread)进行热键注册和卸载和事件监听回调(多线程)
from threading import Thread import win32gui, win32con from random import randint from ctypes import * from ctypes import wintypes import ctypes import ctypes.wintypes from stopped_able_thread import StoppableThread class MyThread(StoppableThread): RegisterHotKey = windll.user32.RegisterHotKey UnregisterHotKey = windll.user32.UnregisterHotKey def __init__(self, *args, **kwargs): ''' :param func: 可调用的对象 :param args: 可调用对象的参数 ''' StoppableThread.__init__(self) self.title = kwargs.get('title') self.ids = {} self.state = False # 每个热键的初始状态 VK_WIN_F10 = [win32con.MOD_WIN, win32con.VK_F10, hide, (), {"title": self.title}] VK_F11 = [0, win32con.VK_F11, full_screen, (), {"title": self.title}] VK_ESC = [0, win32con.VK_ESCAPE, restore_screen, (), {"title": self.title}] VK_F9 = [0, win32con.VK_F9, foreground_window, (), {"title": self.title}] self.keys = [VK_WIN_F10, VK_F11, VK_F9, VK_ESC] def _regster(self, flagid, fnkey, vkey): try: # self, hwnd=None, flagid=0, fnkey=win32con.MOD_ALT, vkey=win32con.VK_F9 # win32gui.RegisterHotKey(0, flagid, fnkey, vkey) self.RegisterHotKey(0, flagid, fnkey, vkey) except Exception as e: print(e.__str__()) def _random_int(self): # flagid 需要int id = randint(1, 999999999) if id in self.ids: return self._random_int() return id def _fast_register(self, key): id = self._random_int() self._regster(id, key[0], key[1]) # 添加热键状态 key.append(self.state) self.ids[id] = key print(id) def _bat_regster(self): for key in self.keys: self._fast_register(key) def run(self): self._bat_regster() msg = ctypes.wintypes.MSG() while 1: if windll.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0: if msg.message == win32con.WM_HOTKEY: id = msg.wParam if id in self.ids: print(id) func = self.ids[id][2] args = self.ids[id][3] kwargs = self.ids[id][4] # func(*arg, **kwargs) self.thread_it(func=func, *args, **kwargs) def __del__(self): if self.is_alive(): try: [self.UnregisterHotKey(0, id) for id in self.ids.keys()] except Exception as e: print(e.__str__()) finally: self.ids.clear() self.stop() def thread_it(self, func, *args, **kwargs): t = Thread(target=func, args=args, kwargs=kwargs) t.setDaemon(True) t.start()
5、tkinter窗体使用守护线程(HotKeyThread)
import tkinter as tk from stoppedTest import hide, MyThread master = tk.Tk() tk.Label(text="show .......").pack() separator = tk.Frame(width=2, height=102, bd=1, relief=tk.SUNKEN) separator.pack(fill=tk.Y, padx=5, pady=5) hotkey = MyThread(title= master.title()) hotkey.setDaemon(True) # 设置守护线程,当线程结束,守护线程同时关闭,要不然这个线程会一直运行下去。 hotkey.start() master.mainloop()
展示
win+F10:窗体隐藏
F9: 窗体置顶
F11 窗体最大化
ESC:最大化后恢复正常状态窗体