我该如何准确地获取我想聚焦的那个窗口的名称,而不必再挨个儿筛选它们?
在不需要把我打开的所有窗口一个个翻来覆去地筛选的情况下,如何找到一个特定的窗口?我只是想获取我打开的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。当开发者使用 CreateWindowW 或 CreateWindowExW 创建这些子控件时,应该通过 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)