我该如何准确地获取我想聚焦的那个窗口的名称,而不必再挨个儿筛选它们?

后端开发 2026-07-11

在不需要把我打开的所有窗口一个个翻来覆去地筛选的情况下,如何找到一个特定的窗口?我只是想获取我打开的Spotify窗口的名称,但我之前写的解决方案并不好,常常会把其他打开的窗口也混入进来。我想能够跟踪我正在听的音乐的艺术家名和歌曲标题。我之所以这样做,是因为我搜索了“如何获取已打开特定窗口的名称”,找了一阵子,发现这是我找到的最佳方法。

import pygetwindow as a
import os, time

#Just a few words that it'll ignore
ignore = [
    "Spotify", "Firefox", "Steam", "Discord", "File explorer", "Notepad"
]

while True:
    titles = a.getAllTitles()

    for b in titles:
        #I check for the hyphen because when a song is playing in Spotify it will always be "artist" - "song name" so I thought that might filter some unwanted stuff out
        if " - " in b:
            if not any(c.lower() in b.lower() for c in ignore):
                print(b)
                #IDK if its wierd to do this but I just don't like the terminal being overflowed with stuff
                time.sleep(1)
                os.system('cls' if os.name == 'nt' else 'clear')

我只是想知道这是不是唯一的办法(我想这应该不是),还是有某种更简单的方式是我没想到的。

解决方案

The issue you're running into isn't actually a problem with PyWin32; it's a quirk of how the underlying Windows API function, EnumWindows, behaves.

在Windows中,几乎每个UI元素——按钮、文本字段、菜单、下拉菜单和单选按钮——从技术上讲都被视为“窗口”,并被分配了自己的 hWnd。当开发者使用 CreateWindowWCreateWindowExW 创建这些子控件时,应该通过 hWndParent 参数把它们绑定到父窗口。

如果他们没这样做(这其实非常普遍),EnumWindows 将把这些孤立的按钮和文本字段视为顶层窗口并返回它们。因此,EnumWindows 通常会被大量不可见的垃圾句柄淹没,使得仅仅查找基本应用程序窗口时变得过于不可靠。

一个更干净的替代方案是 pyvda。它使用专门用于管理虚拟桌面的COM接口。因此,它自然地过滤掉杂乱,只返回实际、在屏幕上可见的父窗口的 hWnd

以下类将返回与 'Spotify' 可执行文件相关联的任意父窗口。我还去除了对 PyWin32 的需求,改用Python内置的 ctypes 来访问Windows API。

import ctypes
from ctypes import wintypes

from psutil import Process
from pyvda import AppView
from pyvda.com_defns import IApplicationView
from pyvda.utils import Managers


class AppProcess(Process, AppView):
    __MANAGERS = Managers()

    @property
    def caption(self) -> str:
        text_len = self.__GetWindowTextLength(self.hwnd) + 1
        text_buffer = ctypes.create_unicode_buffer(text_len)

        self.__GetWindowText(self.hwnd, text_buffer, text_len)
        return text_buffer.value

    def __init__(self, view: IApplicationView):
        self.__init_win32_api_function_prototypes()

        AppView.__init__(self, view = view)
        Process.__init__(self, pid = self.__get_pid(self.hwnd))

    @classmethod
    def find_instances(cls, name: str) -> list[AppProcess]:
        instances: list[AppProcess] = []
        for view in cls.__MANAGERS.view_collection.GetViewsByZOrder().iter(IApplicationView):
            if bool(view.GetShowInSwitchers()):
                if (instance := cls(view = view)).name() == name:
                    instances.append(instance)
        return instances

    def __get_pid(self, hWnd: int) -> int:
        pid = wintypes.DWORD()
        tid = self.__GetWindowThreadProcessId(hWnd, ctypes.byref(pid))
        if tid == 0:
            raise ctypes.WinError(ctypes.get_last_error())
        return pid.value

    def __init_win32_api_function_prototypes(self):
        user32 = ctypes.WinDLL('user32', use_last_error = True)

        # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
        # DWORD GetWindowThreadProcessId(
        #   [in]            HWND    hWnd,
        #   [out, optional] LPDWORD lpdwProcessId
        # );
        self.__GetWindowThreadProcessId = user32.GetWindowThreadProcessId
        self.__GetWindowThreadProcessId.argtypes = [wintypes.HWND, wintypes.LPDWORD]
        self.__GetWindowThreadProcessId.restype = wintypes.DWORD

        # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw
        # int GetWindowTextW(
        #   [in]  HWND   hWnd,
        #   [out] LPWSTR lpString,
        #   [in]  int    nMaxCount
        # );
        self.__GetWindowText = user32.GetWindowTextW
        self.__GetWindowText.argtypes = [wintypes.HWND, wintypes.LPWSTR, wintypes.INT]
        self.__GetWindowText.restype = wintypes.BOOL

        # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextlengthw
        # int GetWindowTextLengthW(
        #   [in] HWND hWnd
        # );
        self.__GetWindowTextLength = user32.GetWindowTextLengthW
        self.__GetWindowTextLength.argtypes = [wintypes.HWND]
        self.__GetWindowTextLength.restype = wintypes.INT


if __name__ == '__main__':
    for process in AppProcess.find_instances(name = 'Spotify.exe'):
        print(process.caption)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章