C# WPF获取顶层活动窗口子内容无障碍信息的实现方法
C# WPF 获取前台窗口子元素无障碍信息实现方案
你之前用的EnumChildWindows只能枚举带有独立HWND句柄的传统Win32控件,无法覆盖WPF、Electron、Chrome/Edge、Qt等现代应用内无独立句柄的按钮、链接、图片等元素,要获取标准无障碍信息,直接使用系统原生的Windows UI Automation(UIA)接口即可,这是系统提供给读屏软件等无障碍工具的标准通道,能覆盖所有支持无障碍规范的应用内容。
前置准备
在你的WPF项目中添加两个系统自带程序集的引用:
- 右键项目 → 添加 → 引用 → 程序集 → 框架,勾选
UIAutomationClient和UIAutomationTypes,确认添加即可,无需安装第三方依赖。
完整实现代码
首先补充代码文件需要的using引用:
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; using System.Runtime.InteropServices; using System.Windows.Automation;
替换你原有MainWindow类的代码,保留原有获取窗口标题的逻辑,新增子元素遍历能力:
public partial class MainWindow : Window { [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", EntryPoint = "GetWindowTextW", CharSet = CharSet.Unicode)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int maxLength); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); private readonly ListBox lst = new ListBox(); // 缓存自身窗口句柄,避免遍历到自己造成死循环 private IntPtr _selfHwnd; public MainWindow() { InitializeComponent(); _selfHwnd = new WindowInteropHelper(this).Handle; DispatcherTimer dispatcherTimer = new DispatcherTimer(); dispatcherTimer.Tick += DispatcherTimer_Tick; dispatcherTimer.Interval = new TimeSpan(0, 0, 5); dispatcherTimer.Start(); grid.Children.Add(lst); } private void DispatcherTimer_Tick(object sender, EventArgs e) { IntPtr foregroundHwnd = GetForegroundWindow(); // 跳过自身窗口 if (foregroundHwnd == _selfHwnd) return; string title = GetWindowTitle(foregroundHwnd); if (string.IsNullOrEmpty(title)) return; lst.Items.Clear(); lst.Items.Add($"=== 当前激活窗口:{title} ==="); lst.Items.Add("=== 无障碍元素列表 ==="); var elements = GetAccessibleElements(foregroundHwnd); foreach (var eleInfo in elements) { lst.Items.Add(eleInfo); } } /// <summary> /// 获取指定窗口句柄下所有层级的无障碍元素信息 /// </summary> private List<string> GetAccessibleElements(IntPtr hWnd) { List<string> result = new List<string>(); if (hWnd == IntPtr.Zero) return result; try { AutomationElement root = AutomationElement.FromHandle(hWnd); if (root == null) return result; // 查找所有后代元素,包含所有层级的子控件 var allElements = root.FindAll(TreeScope.Subtree, Condition.TrueCondition); foreach (AutomationElement ele in allElements) { try { StringBuilder info = new StringBuilder(); // 控件类型:按钮、链接、图片、文本、输入框等 info.Append($"[{ele.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")}]"); // 无障碍名称(读屏软件读取的文本) if (!string.IsNullOrWhiteSpace(ele.Current.Name)) { info.Append($" 名称:{ele.Current.Name}"); } // 控件可用状态 info.Append($" 可用:{ele.Current.IsEnabled}"); // 传统Win32控件补充类名 if (ele.Current.NativeWindowHandle != IntPtr.Zero) { StringBuilder className = new StringBuilder(256); GetClassName(ele.Current.NativeWindowHandle, className, 256); if (className.Length > 0) { info.Append($" 类名:{className}"); } } result.Add(info.ToString()); } catch { // 权限不足、元素已销毁等场景直接跳过 continue; } } } catch (Exception ex) { result.Add($"遍历出错:{ex.Message}"); } return result; } /// <summary> /// 获取指定句柄的窗口标题 /// </summary> private string GetWindowTitle(IntPtr hWnd) { const int maxChar = 256; StringBuilder titleBuilder = new StringBuilder(maxChar); if (GetWindowText(hWnd, titleBuilder, maxChar) > 0) { return titleBuilder.ToString(); } return string.Empty; } }
你的XAML代码不需要修改,保持原有Grid即可。
补充说明
- 如果需要获取元素更多属性,比如链接的跳转值、输入框的文本、按钮的快捷键,可以针对对应控件类型调用UIA的对应模式(如
ValuePattern、InvokePattern、LegacyIAccessiblePattern)取值即可。 - 任务管理器、UAC弹窗等高权限系统窗口,需要把你的程序以管理员身份运行才能正常枚举元素,这是Windows UAC安全机制限制。
- 如果只需要枚举可见、可交互的元素,可以替换
FindAll的查询条件,过滤掉不可见、不可交互的元素,减少无效数据。
内容的提问来源于stack exchange,提问作者Rising Developer
相关产品推荐
相关产品推荐

