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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:45:35