You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 20:25:17