Python窗口激活

31

我如何使用Python编程在Windows中自动激活窗口?我正在向其发送按键,并且目前我只是确保它是上次使用的应用程序,然后发送Alt+Tab按键来从DOS控制台切换到它。有更好的方法吗(因为我根据经验已经知道这种方法绝不是万无一失的)?


1
你真的应该告诉我们你正在使用哪个GUI工具包,因为这个功能可能在工具包中。 - Michael Dillon
2
也许他正在尝试激活任何一个打开的窗口? - Sridhar Ratnakumar
8个回答

60
你可以使用win32gui模块来实现这个功能。首先,你需要获得窗口的有效句柄。如果你知道窗口类名或确切的标题,可以使用win32gui.FindWindow。如果不知道,可以使用win32gui.EnumWindows列举所有窗口,并尝试找到正确的窗口。
一旦你有了句柄,就可以使用该句柄调用win32gui.SetForegroundWindow。它将激活该窗口,并准备接收你的按键输入。
以下是一个示例。希望对你有所帮助。
import win32gui
import re


class WindowMgr:
    """Encapsulates some calls to the winapi for window management"""

    def __init__ (self):
        """Constructor"""
        self._handle = None

    def find_window(self, class_name, window_name=None):
        """find a window by its class_name"""
        self._handle = win32gui.FindWindow(class_name, window_name)

    def _window_enum_callback(self, hwnd, wildcard):
        """Pass to win32gui.EnumWindows() to check all the opened windows"""
        if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
            self._handle = hwnd

    def find_window_wildcard(self, wildcard):
        """find a window whose title matches the wildcard regex"""
        self._handle = None
        win32gui.EnumWindows(self._window_enum_callback, wildcard)

    def set_foreground(self):
        """put the window in the foreground"""
        win32gui.SetForegroundWindow(self._handle)


w = WindowMgr()
w.find_window_wildcard(".*Hello.*")
w.set_foreground()

当您同时打开同一应用程序的多个实例时,此方法将无法正常工作,因为您无法区分它们(因为所有内容都基于窗口标题)。这真是遗憾,但我想这可能是由于Win32API而不是特定的Python模块引起的? - dm76
4
HWND代表什么?(缩写真让人头疼!) - NullVoxPopuli
3
2018-11-26。在Windows 10上使用pip 18.0.1安装win32gui时,我遇到了“ModuleNotFoundError:No module named 'win32.distutils.command'”错误。 - Mathieu CAROFF
1
在某些版本的Windows上,SetForegroundWindow可能会导致意外错误。要修复此问题,您需要先发送一个alt键,更多信息 - Yusof Bandar
@myidealab 不知道如何在计算机锁定时设置前景。您可能需要检查Windows API以了解是否可能实现此功能。 - luc
显示剩余3条评论

8

PywinautoSWAPY可能需要最少的努力来设置窗口的焦点

使用SWAPY自动生成Python代码以检索窗口对象,例如:

import pywinauto

# SWAPY will record the title and class of the window you want activated
app = pywinauto.application.Application()
t, c = u'WINDOW SWAPY RECORDS', u'CLASS SWAPY RECORDS'
handle = pywinauto.findwindows.find_windows(title=t, class_name=c)[0]
# SWAPY will also get the window
window = app.window_(handle=handle)

# this here is the only line of code you actually write (SWAPY recorded the rest)
window.SetFocus()

如果其他窗口恰好在所需窗口前面,不用担心。使用此额外代码此代码将确保在运行上述代码之前显示它:
# minimize then maximize to bring this window in front of all others
window.Minimize()
window.Maximize()
# now you can set its focus
window.SetFocus()

4
这个例子一直报错,说在第6行(初始化句柄的那一行)列表索引超出范围。我尝试查找pywinauto.findwindows.find_windows()方法的信息,但文档中没有相关内容。 - EmppuTheEngineer
注意:在安装pywinautogui之前,必须重新启动任何正在运行的Python shell,否则它将在加载时缺少依赖项;Pywinauto无法热插拔。 - Mathieu CAROFF
感谢提供最小化和最大化的技巧。这也解决了我在使用win32gui时遇到的问题。 - Amin Guermazi

2
import ctypes, platform

if platform.system() == 'Windows':
    Active_W = ctypes.windll.user32.GetActiveWindow()
    ctypes.windll.user32.SetWindowPos(Active_W,0,0,0,0,0,0x0002|0x0001)

让我们开始吧。您只需要存储活动窗口的值。


1

使用Pip安装键盘库。

在设置前景窗口之前,模拟按下Esc键,即keyboard.send('esc')。

您可能需要对以下任一项执行三次:

  1. 侧边栏
  2. Windows键叠加层
  3. 始终置顶的任务管理器

我在Windows环境下开发已经有很长一段时间了 :),所以我应该习惯于面对荒谬的问题。尽管如此,当出现这种问题时,我仍然感到惊讶。感谢您的建议,看起来它救了我的一天! - Max1234-ITA

0

GUI应用程序以保持窗口活动


Python3

安装库

pip install pywin32

将下面的代码保存为alive.pyw文件

from ctypes import windll, wintypes, byref, c_uint, sizeof, Structure
import tkinter as tk
import ctypes
import sys
import threading
import time
import win32api
import win32con


stop_threads = True
SET_IDLE_TIME = 40 #in seconds
tm1 = time.time()
value = 0

class LASTINPUTINFO(Structure):
    _fields_ = [
        ('cbSize', c_uint),
        ('dwTime', c_uint),
    ]

def get_idle_duration():
    global value, tm1
    lastInputInfo = LASTINPUTINFO()
    lastInputInfo.cbSize = sizeof(lastInputInfo)
    windll.user32.GetLastInputInfo(byref(lastInputInfo))

    # millis = 4294967 - lastInputInfo.dwTime - windll.kernel32.GetTickCount()
    # print(windll.kernel32.GetTickCount(), lastInputInfo.dwTime, sizeof(lastInputInfo), millis)
    tm2 = time.time() - tm1
    last_idel_time = lastInputInfo.dwTime
    # print()
    if value != last_idel_time:
        value = last_idel_time
        tm2 = time.time() - time.time()
        tm1 = time.time()
    # print("time:", tm1)
    return tm2


def press_key_2():
    global stop_threads, tm1
    while True:
        if not stop_threads:
            break
        idle_time = get_idle_duration() #seconds
        # print(idle_time)
        g = float("{:.2f}".format(idle_time))
        st = str(g) + " / " + str(SET_IDLE_TIME)
        var.set(st)
        time.sleep(0.1)
        if idle_time < SET_IDLE_TIME:
            continue

        print("in ideal state pressing cltr")
        win32api.keybd_event(ord('x'), 0, win32con.KEYEVENTF_EXTENDEDKEY, 0)
        tm1 = time.time()


#---------------- Monitor threads ------------------------------

t1 = threading.Thread(target=press_key_2, name='t1')
t1.daemon = True

#----------------- TK functions ----------------------

def display_on():
    global tk, t1, stop_threads
    stop_threads = True
    print("Always On")
    ctypes.windll.kernel32.SetThreadExecutionState(0x80000002)
    root.iconify()
    t1.start()
    # t2.start()

def display_reset():
    print("quit pressed")
    global stop_threads
    stop_threads = False
    ctypes.windll.kernel32.SetThreadExecutionState(0x80000000)
    sys.exit(0)



root = tk.Tk()
root.geometry("200x110")
root.title("Devil")
frame = tk.Frame(root)
frame.pack()

var = tk.StringVar()
var_idle = tk.StringVar()

label = tk.Label(frame, textvariable =  var)#, bd = 5, justify = tk.RIGHT, padx = 10, pady = 10)
label_idle = tk.Label(frame,textvariable = var_idle)
var_idle.set("Idle Time")
var.set("-")
button = tk.Button(frame,
                   text="Quit",
                   fg="red",
                   command=display_reset)

slogan = tk.Button(frame,
                   text="Always ON",
                   command=display_on)

label_idle.pack(side=tk.BOTTOM,padx=15, pady=13)
label.pack(side=tk.BOTTOM,padx=15, pady=5)

slogan.pack(side=tk.LEFT,padx=15, pady=5)
button.pack(side=tk.LEFT,padx=15, pady=5)


root.mainloop()
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000)

0

如果窗口被最小化(即IsIconic),使用SetWindowPosSetForegroundWindow可能不足够!我们可以使用ShowWindowSW_RESTORE (9)

import ctypes

def activate_window(hwnd):
    user32 = ctypes.windll.user32
    user32.SetForegroundWindow(hwnd)
    if user32.IsIconic(hwnd):
        user32.ShowWindow(hwnd, 9)

根据您识别所需窗口的方式,有一些方法可以获取 hwnd,也就是窗口句柄。
您可以通过 循环遍历所有窗口 来查找正确的句柄,根据 pid 使用 user32.GetWindowThreadProcessId 或使用窗口名称 user32.GetWindowTextW
要获取 ProcessIds,您可以使用 Windows 内置的 wmic。它还有许多其他方便的用途。(所有属性: wmic process get /?) (get handle 已损坏) 例如:
def get_pids(proc_name):
    out = subprocess.check_output('wmic process where Name="%s" get ProcessId' % proc_name)
    pids = out.decode().strip().split()[1:]
    if not pids:
        raise WindowsError('Could not find pids for process')
    return [int(pid) for pid in pids]

0

补充 @luc 的回答,以下是代码如何更详细地处理存在多个窗口时所选的句柄:

pip install pywin32 后运行

import win32gui
import re

class WindowMgr:
  """Encapsulates some calls to the winapi for window management"""

  def __init__ (self):
    """Constructor"""
    self._handle = None
    self._handles = []

  def find_window(self, class_name, window_name=None):
    """find a window by its class_name"""
    self._handle = win32gui.FindWindow(class_name, window_name)

  def _window_enum_callback(self, hwnd, wildcard):
    """Pass to win32gui.EnumWindows() to check all the opened windows"""
    if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
      self._handles.append(hwnd)
      self._handle = hwnd

  def find_window_wildcard(self, wildcard):
    """find a window whose title matches the wildcard regex"""
    self._handle = None
    self._handles = []
    win32gui.EnumWindows(self._window_enum_callback, wildcard)

    self.set_handle()

  def set_foreground(self):
    """put the window in the foreground"""
    if self._handle != None:
      win32gui.SetForegroundWindow(self._handle)
    else:
      print("No handle is selected, couldn't set focus")

  def set_handle(self):
    """get one handle to operate on from all the matched handles"""
    if len(self._handles) < 1:
      print("Matched no window")
      return False

    if len(self._handles) > 1:
      print("Selecting the first handle of multiple windows:")
    else: # len(self._handles) == 1:
      print("Matched a single window:")

    self.print_matches()
    self._handle = self._handles[0]
    return True

  def print_matches(self):
    """print the title of each matched handle"""
    for hwnd in self._handles:
      print("- " + str(win32gui.GetWindowText(hwnd)))

w = WindowMgr()
w.find_window_wildcard(".*Hello.*")
w.set_foreground()

注意:由于建议编辑队列已满,我无法通过编辑@luc的答案进行添加。


0
一个晚点的参与者,但这对我有效。使其工作的关键是使用脚本中的SendKeys()函数从当前窗口中获取焦点。这在使用Python 3.10.6测试的Windows 11上进行了测试。
import re
import win32gui
import win32com.client 


class WindowMgr:
    """ Encapsulates calls to the winapi for window management
        Forces context window to take focus
        Based on: 
         - https://dev59.com/ZnI95IYBdhLWcg3w7SlZ
         - https://dev59.com/SmYq5IYBdhLWcg3wrSas
    """

    def __init__ (self):
        self._handle = None

    def find_window(self, class_name, window_name=None):
        """find a window by its class_name"""
        self._handle = win32gui.FindWindow(class_name, window_name)
        return self

    def _window_enum_callback(self, hwnd, wildcard):
        """Pass to win32gui.EnumWindows() to check all the opened windows"""
        if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
            self._handle = hwnd

    def find_window_wildcard(self, wildcard):
        """find a window whose title matches the wildcard regex"""
        self._handle = None
        win32gui.EnumWindows(self._window_enum_callback, wildcard)
        return self

    def set_foreground(self):
        """put the window in the foreground"""
        shell = win32com.client.Dispatch("WScript.Shell")
        shell.SendKeys('%')  # left shift key sent, this shifts focus from current window
        win32gui.SetForegroundWindow(self._handle)

这个函数的一个简单用法是可以使用链式调用,如下所示:

WindowMgr().find_window_wildcard("My Window Title").set_foreground()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接