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:最大化后恢复正常状态窗体

 

 




 

          

          

posted on 2023-11-24 10:12  Old-Kang  阅读(25)  评论(0编辑  收藏  举报