当任务管理器的 window 获得焦点时,如何使用键盘焦点将应用程序的 window 集中到前台?

How to focus an application's window to the foreground with keyboard focus when the task manager's window is focused?

我有一个 Python 脚本,它创建一个应用程序的实例并显示应用程序的 window。

我正在尝试 activate/focus window 以便将它带到 foreground/top 并获得键盘输入焦点。

下面的代码通常可以工作,但是当任务管理器的 window 在代码执行之前打开并聚焦时,应用程序的 window 出现在任务管理器下方并且任务管理器保留键盘输入焦点。

代码中的注释是我试图规避特定问题的尝试,但也没有奏效。只有当 SwitchToThisWindowFalseSetWindowPosHWND_TOPMOST 一起使用时(将 window 设置为最顶部),window 才会出现在顶部任务管理器的 window,但任务管理器仍保持键盘输入焦点。

def bring_window_to_top(window_handle):
  import ctypes
  # import win32com.client
  # from win32con import HWND_TOP, HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE

  current_thread_id = ctypes.windll.kernel32.GetCurrentThreadId()
  foreground_window_handle = ctypes.windll.user32.GetForegroundWindow()
  foreground_thread_id = ctypes.windll.user32.GetWindowThreadProcessId(foreground_window_handle, None)
  ctypes.windll.user32.AttachThreadInput(current_thread_id, foreground_thread_id, True)
  ctypes.windll.user32.BringWindowToTop(window_handle)
  # ctypes.windll.user32.SwitchToThisWindow(window_handle, True)
  # ctypes.windll.user32.SwitchToThisWindow(window_handle, False)
  # ctypes.windll.user32.SetWindowPos(window_handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)
  # ctypes.windll.user32.SetWindowPos(window_handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)
  # wscript_shell = win32com.client.Dispatch('WScript.Shell')
  # wscript_shell.SendKeys('%')
  # ctypes.windll.user32.SetForegroundWindow(window_handle)
  # ctypes.windll.user32.SetFocus(window_handle)
  # ctypes.windll.user32.SetActiveWindow(window_handle)
  # ctypes.windll.user32.AttachThreadInput(current_thread_id, foreground_thread_id, False)

我也曾尝试使用函数 AllowSetForegroundWindowLockSetForegroundWindowSystemParametersInfoWSPI_SETFOREGROUNDLOCKTIMEOUT 设置为 0 但我得到错误 Access denied. 来自 ctypes.FormatError().

有什么办法可以实现吗?

您需要在清单中将 UIAccess 设置为 true 以支持辅助功能。

A process that is started with UIAccess rights has the following abilities:

  • Set the foreground window.
  • Drive any application window by using the SendInput function.
  • Use read input for all integrity levels by using low-level hooks, raw input, GetKeyState, GetAsyncKeyState, and GetKeyboardInput.
  • Set journal hooks.
  • Use AttachThreadInput to attach a thread to a higher integrity input queue.

首先,在清单中设置uiAccess=true

然后,签署代码。

最后,将其放在文件系统的安全位置:

  • \Program Files\ 包括子目录
  • \Windows\system32\
  • \Program Files (x86)\ 包括 64 位版本的子目录 Windows

你可以参考this document and this answer.

更新:

将 UIAccess 设置为 python 脚本:

  1. 安装 PyInstaller:pip install pyinstaller.
  2. 使用 Pyinstaller 从 Python 脚本生成可执行文件。

这是我的测试示例,它设置了一个 5 秒的计时器来使 window 到达顶部。

hello.pyw:

import win32api, win32con, win32gui
import ctypes
class MyWindow:
        
    def __init__(self):
        win32gui.InitCommonControls()
        self.hinst = win32api.GetModuleHandle(None)
        className = 'MyWndClass'
        message_map = {
            win32con.WM_DESTROY: self.OnDestroy,
            win32con.WM_TIMER: self.OnTimer,
        }
        wndcls = win32gui.WNDCLASS()
        wndcls.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
        wndcls.lpfnWndProc = message_map
        wndcls.lpszClassName = className
        win32gui.RegisterClass(wndcls)
        style = win32con.WS_OVERLAPPEDWINDOW
        self.hwnd = win32gui.CreateWindow(
            className,
            'Title',
            style,
            win32con.CW_USEDEFAULT,
            win32con.CW_USEDEFAULT,
            500,
            500,
            0,
            0,
            self.hinst,
            None
        )
        win32gui.UpdateWindow(self.hwnd)
        win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
        ctypes.windll.user32.SetTimer(self.hwnd,1,5000,0)
    def OnDestroy(self, hwnd, message, wparam, lparam):
        win32gui.PostQuitMessage(0)
        return True
    def OnTimer(self, hwnd, message, wparam, lparam):
        current_thread_id = ctypes.windll.kernel32.GetCurrentThreadId()
        foreground_window_handle = ctypes.windll.user32.GetForegroundWindow()
        foreground_thread_id = ctypes.windll.user32.GetWindowThreadProcessId(foreground_window_handle, None)
        ctypes.windll.user32.BringWindowToTop(hwnd)
        return True

w = MyWindow()
win32gui.PumpMessages()

使用--manifest <FILE or XML>选项或直接使用pyinstaller --uac-uiaccess hello.pyw,则exe文件位于dist\hello

  1. 创建证书并签署应用程序示例(并且不要忘记将证书安装到 Trusted Root Certication Authorities):
  2. 放在文件系统的安全位置,比如我把dist\hello放在C:\Program Files

结果: