# -*- coding: utf-8 -*- """ 打印PC微信通讯录的所有子控件 需要以管理员权限运行Python """ import os import uiautomation as auto import time import ctypes from ctypes import wintypes, POINTER, byref, c_int, c_ulong, c_void_p import comtypes.client def print_control_tree(control, depth=0, max_depth=20, file=None): """递归打印控件树""" if depth > max_depth: return # 获取控件信息 control_type = control.ControlTypeName class_name = control.ClassName or "" name = control.Name or "" automation_id = control.AutomationId or "" rect = control.BoundingRectangle # 缩进 indent = " " * depth # 打印控件信息 line = f"{indent}ControlType: {control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect} Depth: {depth}" print(line) if file: file.write(line + "\n") # 递归打印子控件 try: children = control.GetChildren() for child in children: print_control_tree(child, depth + 1, max_depth, file) except Exception as e: error_line = f"{indent} [Error getting children: {e}]" print(error_line) if file: file.write(error_line + "\n") def print_control_tree_raw_comtypes(uiAutomation, element, depth=0, max_depth=20, file=None): """使用comtypes直接访问IUIAutomation接口""" if depth > max_depth: return try: # 获取控件属性 name = element.CurrentName or "" class_name = element.CurrentClassName or "" automation_id = element.CurrentAutomationId or "" control_type_id = element.CurrentControlType localized_control_type = element.CurrentLocalizedControlType or "" # 获取BoundingRectangle try: rect = element.CurrentBoundingRectangle rect_str = f"({rect.left},{rect.top},{rect.right},{rect.bottom})" except: rect_str = "N/A" indent = " " * depth line = f"{indent}ControlType: {localized_control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect_str} Depth: {depth}" print(line) if file: file.write(line + "\n") # 使用RawViewWalker遍历子元素 tree_walker = uiAutomation.RawViewWalker child = tree_walker.GetFirstChildElement(element) while child: print_control_tree_raw_comtypes(uiAutomation, child, depth + 1, max_depth, file) # 获取下一个兄弟元素 child = tree_walker.GetNextSiblingElement(child) except Exception as e: indent = " " * depth error_line = f"{indent}[Error: {e}]" print(error_line) if file: file.write(error_line + "\n") def enum_windows_callback(hwnd, results): """枚举窗口回调函数""" import win32gui import win32process if win32gui.IsWindowVisible(hwnd): class_name = win32gui.GetClassName(hwnd) title = win32gui.GetWindowText(hwnd) _, pid = win32process.GetWindowThreadProcessId(hwnd) # 检查是否是微信相关窗口 try: import subprocess result = subprocess.run(['tasklist', '/FI', f'PID eq {pid}', '/NH'], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW) if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower(): results.append((hwnd, class_name, title, pid)) except: pass return True def print_win32_children(hwnd, depth=0, max_depth=20, file=None): """使用Win32 API枚举子窗口""" import win32gui if depth > max_depth: return class_name = win32gui.GetClassName(hwnd) title = win32gui.GetWindowText(hwnd) rect = win32gui.GetWindowRect(hwnd) indent = " " * depth line = f"{indent}HWND: 0x{hwnd:X} ClassName: {class_name} Title: {title} Rect: {rect} Depth: {depth}" print(line) if file: file.write(line + "\n") # 枚举子窗口 def child_callback(child_hwnd, _): print_win32_children(child_hwnd, depth + 1, max_depth, file) return True try: win32gui.EnumChildWindows(hwnd, child_callback, None) except: pass def print_accessible_tree(hwnd, depth=0, max_depth=20, file=None): """使用IAccessible接口枚举控件""" import ctypes from ctypes import POINTER, byref from ctypes import windll, oledll if depth > max_depth: return try: import comtypes.client from comtypes import IUnknown # 获取IAccessible接口 accessible = oledll.oleacc.AccessibleObjectFromWindow( hwnd, 0xFFFFFFFC, # OBJID_CLIENT comtypes.IUnknown._iid_, byref(ctypes.POINTER(comtypes.IUnknown)()) ) # 尝试获取更多信息 indent = " " * depth line = f"{indent}HWND: 0x{hwnd:X} [Accessible Object Available]" print(line) if file: file.write(line + "\n") except Exception as e: indent = " " * depth line = f"{indent}HWND: 0x{hwnd:X} [No IAccessible: {e}]" print(line) if file: file.write(line + "\n") def main(): print("=" * 80) print("打印PC微信通讯录的所有子控件") print("=" * 80) print() # 设置全局搜索超时时间 auto.SetGlobalSearchTimeout(10) # 获取桌面根控件 root = auto.GetRootControl() print("桌面根控件:", root) print() # 查找微信主窗口 - 尝试多种方式 print("正在查找微信窗口...") wechat_window = None # 方式1: 通过ClassName查找 (旧版微信) wechat_window = auto.WindowControl(searchDepth=1, ClassName='WeChatMainWndForPC') if wechat_window.Exists(2, 1): print(f"方式1找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}") else: # 方式2: 通过Name查找 wechat_window = auto.WindowControl(searchDepth=1, Name='微信') if wechat_window.Exists(2, 1): print(f"方式2找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}") else: # 方式3: 通过进程名查找 (新版微信 WeChatAppEx.exe) print("尝试通过进程查找微信窗口...") for window in root.GetChildren(): try: process_id = window.ProcessId import subprocess result = subprocess.run(['tasklist', '/FI', f'PID eq {process_id}', '/NH'], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW) if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower(): wechat_window = window print(f"方式3找到微信窗口: {window.Name}, ClassName: {window.ClassName}, PID: {process_id}") break except Exception as e: continue if wechat_window is None or not wechat_window.Exists(1, 1): print("\n未找到微信窗口!请确保:") print("1. 微信已打开并登录") print("2. 以管理员权限运行此脚本") print("\n尝试打印所有顶级窗口以帮助调试...") print("=" * 80) for window in root.GetChildren(): try: print(f"Window: Name='{window.Name}' ClassName='{window.ClassName}' PID={window.ProcessId}") except: pass return print() # 打印微信窗口的基本信息 print("=" * 80) print("微信主窗口信息:") print("=" * 80) print(f"Name: {wechat_window.Name}") print(f"ClassName: {wechat_window.ClassName}") print(f"AutomationId: {wechat_window.AutomationId}") print(f"ControlType: {wechat_window.ControlTypeName}") print(f"ProcessId: {wechat_window.ProcessId}") print(f"Handle: {wechat_window.NativeWindowHandle}") print() # 输出文件 output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_output.txt') # 首先尝试标准方法 print("=" * 80) print("方式1: 使用标准UIAutomation遍历控件...") print("=" * 80) with open(output_file, 'w', encoding='utf-8') as f: f.write("=" * 80 + "\n") f.write("微信窗口控件树 - 标准UIAutomation\n") f.write("=" * 80 + "\n\n") f.write(f"微信窗口: Name={wechat_window.Name}, ClassName={wechat_window.ClassName}\n\n") print_control_tree(wechat_window, depth=0, max_depth=20, file=f) print() print(f"标准方法输出已保存到: {output_file}") print() # 尝试使用RawViewWalker (可以获取更多控件) print("=" * 80) print("方式2: 使用RawViewWalker遍历控件 (包括原始控件)...") print("=" * 80) output_file_raw = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_raw.txt') try: # 加载UIAutomation类型库 comtypes.client.GetModule('UIAutomationCore.dll') from comtypes.gen.UIAutomationClient import IUIAutomation, CUIAutomation # 创建IUIAutomation实例 uiAutomation = comtypes.CoCreateInstance(CUIAutomation._reg_clsid_, interface=IUIAutomation) # 获取RootElement root_element = uiAutomation.GetRootElement() # 找到微信窗口元素 condition = uiAutomation.CreatePropertyCondition(30005, "微信") # UIA_NamePropertyId = 30005 # 查找微信窗口 (TreeScope_Children = 2) wechat_element = root_element.FindFirst(2, condition) if wechat_element: with open(output_file_raw, 'w', encoding='utf-8') as f: f.write("=" * 80 + "\n") f.write("微信窗口控件树 - RawViewWalker\n") f.write("=" * 80 + "\n\n") print_control_tree_raw_comtypes(uiAutomation, wechat_element, depth=0, max_depth=20, file=f) print(f"\nRawViewWalker输出已保存到: {output_file_raw}") else: print("使用RawViewWalker未找到微信窗口元素") except Exception as e: print(f"RawViewWalker方法出错: {e}") import traceback traceback.print_exc() print() # 尝试使用Win32 API枚举窗口 print("=" * 80) print("方式3: 使用Win32 API枚举所有子窗口...") print("=" * 80) try: import win32gui import win32process hwnd = wechat_window.NativeWindowHandle output_file_win32 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_win32.txt') with open(output_file_win32, 'w', encoding='utf-8') as f: f.write("=" * 80 + "\n") f.write("微信窗口控件树 - Win32 API\n") f.write("=" * 80 + "\n\n") print_win32_children(hwnd, depth=0, max_depth=20, file=f) print(f"\nWin32 API输出已保存到: {output_file_win32}") except ImportError: print("未安装pywin32,跳过Win32 API方式") print("可以通过 'pip install pywin32' 安装") except Exception as e: print(f"Win32 API方法出错: {e}") import traceback traceback.print_exc() print() print("=" * 80) print("完成!") print("=" * 80) print() print("说明:") print("新版微信(WeChatAppEx.exe)使用CEF/Chromium渲染UI,") print("其内部控件不通过Windows原生控件实现,") print("因此UIAutomation和Win32 API都无法直接访问其内部控件。") print() print("如需获取微信控件信息,可以尝试:") print("1. 使用Microsoft Accessibility Insights工具") print("2. 使用Chrome DevTools Protocol (如果微信支持)") print("3. 使用OCR或图像识别技术") if __name__ == '__main__': main()