如何用Python 3.9获取Windows 11桌面图标坐标?
获取Windows 11桌面图标坐标的问题排查与解决
问题概述
使用Python 3.9获取Windows 11系统桌面图标的(x,y)坐标时,调用LVM_GETITEMPOSITION消息返回所有图标坐标均为(0,0),但LVM_GETITEMCOUNT能正常获取图标数量,需确认该方法的可行性并寻找更优实现方案。
原代码与输出
原代码
import ctypes from ctypes import * from commctrl import LVM_GETITEMCOUNT, LVM_GETITEMPOSITION import pywintypes from uint import Uint import win32gui GetShellWindow = ctypes.windll.user32.GetShellWindow class POINT(Structure): _fields_ = [("x", c_int), ("y", c_int)] def get_desktop(): shell_window = GetShellWindow() shell_dll_defview = win32gui.FindWindowEx(shell_window, 0, "SHELLDLL_DefView", "") if shell_dll_defview == 0: sys_listview_container = [] try: win32gui.EnumWindows(_callback, sys_listview_container) except pywintypes.error as e: if e.winerror != 0: raise sys_listview = sys_listview_container[0] else: sys_listview = win32gui.FindWindowEx(shell_dll_defview, 0, "SysListView32", "FolderView") return sys_listview def _callback(hwnd, extra): class_name = win32gui.GetClassName(hwnd) if class_name == "WorkerW": child = win32gui.FindWindowEx(hwnd, 0, "SHELLDLL_DefView", "") if child != 0: sys_listview = win32gui.FindWindowEx(child, 0, "SysListView32", "FolderView") extra.append(sys_listview) return False return True def get_item_count(desktop): return win32gui.SendMessage(desktop, LVM_GETITEMCOUNT) def get_item(desktop, point_buffer, icon_count): if desktop: for i in range(icon_count): point_buffer = ctypes.pointer(ctypes.wintypes.POINT(0, 0)) ret = win32gui.SendMessage(desktop, LVM_GETITEMPOSITION, i, point_buffer) if ret != 0: x = point_buffer.contents.x y = point_buffer.contents.y print("x, y: ", x,y) desktop = get_desktop() icon_count = get_item_count(desktop) point = POINT() point_buffer = pointer(point) get_item(desktop, point_buffer, icon_count)
输出
x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0 x, y: 0 0
问题原因
LVM_GETITEMPOSITION要求传入的POINT结构指针必须属于目标窗口(桌面SysListView32控件)所属进程(explorer.exe)的地址空间。原代码传递的是Python进程本地的内存指针,explorer.exe无法访问该区域,因此无法写入真实坐标,最终返回默认的(0,0)。
修复后的ListView消息方案
通过跨进程内存操作解决该问题,步骤包括:在目标进程分配内存、传递目标进程内存地址获取坐标、读取目标进程内存到本地,代码如下:
import ctypes from ctypes import * from commctrl import LVM_GETITEMCOUNT, LVM_GETITEMPOSITION import pywintypes import win32gui import win32process import win32api import win32con GetShellWindow = ctypes.windll.user32.GetShellWindow class POINT(Structure): _fields_ = [("x", c_int), ("y", c_int)] def get_desktop(): shell_window = GetShellWindow() shell_dll_defview = win32gui.FindWindowEx(shell_window, 0, "SHELLDLL_DefView", "") if shell_dll_defview == 0: sys_listview_container = [] try: win32gui.EnumWindows(_callback, sys_listview_container) except pywintypes.error as e: if e.winerror != 0: raise sys_listview = sys_listview_container[0] else: sys_listview = win32gui.FindWindowEx(shell_dll_defview, 0, "SysListView32", "FolderView") return sys_listview def _callback(hwnd, extra): class_name = win32gui.GetClassName(hwnd) if class_name == "WorkerW": child = win32gui.FindWindowEx(hwnd, 0, "SHELLDLL_DefView", "") if child != 0: sys_listview = win32gui.FindWindowEx(child, 0, "SysListView32", "FolderView") extra.append(sys_listview) return False return True def get_item_count(desktop): return win32gui.SendMessage(desktop, LVM_GETITEMCOUNT) def get_item_positions(desktop, icon_count): if not desktop: return # 获取目标窗口所属进程ID pid = win32process.GetWindowThreadProcessId(desktop)[1] # 打开进程并获取内存操作权限 process_handle = win32api.OpenProcess( win32con.PROCESS_VM_OPERATION | win32con.PROCESS_VM_READ | win32con.PROCESS_VM_WRITE, False, pid ) # 在目标进程分配内存存储POINT结构 buffer_size = ctypes.sizeof(POINT) remote_buffer = win32api.VirtualAllocEx( process_handle, 0, buffer_size, win32con.MEM_COMMIT | win32con.MEM_RESERVE, win32con.PAGE_READWRITE ) try: local_point = POINT() for i in range(icon_count): # 发送消息获取坐标到目标进程内存 ret = win32gui.SendMessage(desktop, LVM_GETITEMPOSITION, i, remote_buffer) if ret: # 读取目标进程内存到本地 win32api.ReadProcessMemory( process_handle, remote_buffer, ctypes.byref(local_point), buffer_size, None ) print(f"图标{i}坐标: ({local_point.x}, {local_point.y})") finally: # 释放内存并关闭进程句柄 win32api.VirtualFreeEx(process_handle, remote_buffer, 0, win32con.MEM_RELEASE) win32api.CloseHandle(process_handle) # 执行逻辑 desktop = get_desktop() icon_count = get_item_count(desktop) get_item_positions(desktop, icon_count)
更优替代方案:Shell API直接枚举
无需跨进程内存操作,通过Shell API直接枚举桌面项并获取坐标,稳定性更高:
import win32com.client import win32gui def get_desktop_icon_positions(): shell = win32com.client.Dispatch("Shell.Application") desktop_folder = shell.Namespace(0) desktop_hwnd = win32gui.GetDesktopWindow() for item in desktop_folder.Items(): item_name = item.Name # 获取图标相对于桌面客户区的坐标 client_pos = item.GetFolderItem().GetIconLocation()[1] # 转换为屏幕坐标 screen_pos = win32gui.ClientToScreen(desktop_hwnd, client_pos) print(f"图标'{item_name}'屏幕坐标: {screen_pos}") get_desktop_icon_positions()
内容的提问来源于stack exchange,提问作者thushara b
相关产品推荐
相关产品推荐

