You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

技术求助:如何获取Windows窗口内的全部文本?现有代码仅能获取窗口标题

如何获取窗口内的所有文本

嘿,问题出在你用的GetWindowText上——这个API本来就是专门用来获取窗口标题栏文本的,窗口内部的实际内容属于各种子控件(比如文本框、静态标签、富文本框这些),所以得换个思路来抓取这些内容。下面给你两种可行的方案,分别适配不同类型的应用:

方案一:针对传统Win32应用(用标准控件的程序)

这类应用的UI是基于Win32控件构建的,我们可以遍历窗口的所有子控件,然后给每个控件发送WM_GETTEXT消息来获取文本。

步骤1:导入必要的Win32 API

先添加这些DllImport定义:

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class WindowTextGrabber
{
    // 枚举子控件的委托
    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

    // Win32消息常量
    private const uint WM_GETTEXT = 0x000D;
    private const uint WM_GETTEXTLENGTH = 0x000E;

步骤2:编写获取所有控件文本的方法

这个方法会递归遍历所有子控件,筛选出文本类控件并提取内容:

public static List<string> GetAllWin32WindowText(IntPtr mainWindowHandle)
    {
        List<string> allTextContent = new List<string>();

        // 枚举主窗口的所有子控件
        EnumChildWindows(mainWindowHandle, (childHandle, lParam) =>
        {
            // 获取控件的类名,判断是否是文本类控件
            StringBuilder className = new StringBuilder(256);
            GetClassName(childHandle, className, className.Capacity);
            string classStr = className.ToString();

            // 常见的文本控件类型:Edit(文本框)、Static(静态标签)、RichEdit(富文本)
            if (classStr.Equals("Edit", StringComparison.OrdinalIgnoreCase) ||
                classStr.Equals("Static", StringComparison.OrdinalIgnoreCase) ||
                classStr.Contains("RichEdit"))
            {
                // 获取文本长度
                int textLength = SendMessage(childHandle, WM_GETTEXTLENGTH, 0, null);
                if (textLength > 0)
                {
                    StringBuilder sb = new StringBuilder(textLength + 1);
                    SendMessage(childHandle, WM_GETTEXT, sb.Capacity, sb);
                    allTextContent.Add(sb.ToString());
                }
            }

            // 递归遍历子控件的子控件(比如嵌套的控件)
            EnumChildWindows(childHandle, (grandChildHandle, gParam) =>
            {
                StringBuilder gcClassName = new StringBuilder(256);
                GetClassName(grandChildHandle, gcClassName, gcClassName.Capacity);
                string gcClassStr = gcClassName.ToString();

                if (gcClassStr.Equals("Edit", StringComparison.OrdinalIgnoreCase) ||
                    gcClassStr.Equals("Static", StringComparison.OrdinalIgnoreCase) ||
                    gcClassStr.Contains("RichEdit"))
                {
                    int gcTextLength = SendMessage(grandChildHandle, WM_GETTEXTLENGTH, 0, null);
                    if (gcTextLength > 0)
                    {
                        StringBuilder gcSb = new StringBuilder(gcTextLength + 1);
                        SendMessage(grandChildHandle, WM_GETTEXT, gcSb.Capacity, gcSb);
                        allTextContent.Add(gcSb.ToString());
                    }
                }

                return true;
            }, IntPtr.Zero);

            return true;
        }, IntPtr.Zero);

        return allTextContent;
    }
}

步骤3:在你的代码中调用这个方法

替换原来的逻辑,直接用进程的主窗口句柄调用:

Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
    if (!string.IsNullOrEmpty(process.MainWindowTitle))
    {
        Console.WriteLine($"=== 窗口标题: {process.MainWindowTitle} ===");
        List<string> content = WindowTextGrabber.GetAllWin32WindowText(process.MainWindowHandle);
        
        if (content.Count > 0)
        {
            Console.WriteLine("窗口内容:");
            foreach (string text in content)
            {
                Console.WriteLine($"- {text}");
            }
        }
        else
        {
            Console.WriteLine("未找到可提取的文本内容(可能是现代应用,建议用方案二)");
        }
        Console.WriteLine();
    }
}

方案二:通用方案(支持Win32/WPF/UWP/浏览器等现代应用)

如果是WPF、UWP、Chrome/Edge这类现代应用,它们的UI不是用标准Win32控件构建的,方案一就失效了。这时候推荐用Windows UI Automation——微软官方的自动化框架,能几乎支持所有类型的应用。

步骤1:添加引用

在你的项目中添加两个引用:

  • UIAutomationClient
  • UIAutomationTypes

步骤2:编写UI Automation的文本提取方法

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Automation;

public class UIAutomationTextGrabber
{
    public static List<string> GetAllWindowText(IntPtr mainWindowHandle)
    {
        List<string> allTextContent = new List<string>();

        // 通过窗口句柄获取Automation元素
        AutomationElement windowElement = AutomationElement.FromHandle(mainWindowHandle);
        if (windowElement == null)
            return allTextContent;

        // 用控件视图遍历器遍历所有子元素
        TreeWalker controlWalker = TreeWalker.ControlViewWalker;
        AutomationElement currentElement = controlWalker.GetFirstChild(windowElement);

        TraverseElements(currentElement, allTextContent, controlWalker);

        return allTextContent;
    }

    // 递归遍历所有子元素
    private static void TraverseElements(AutomationElement element, List<string> textList, TreeWalker walker)
    {
        if (element == null)
            return;

        // 获取元素的Name属性(通常对应显示的文本)
        string elementText = element.Current.Name;
        if (!string.IsNullOrEmpty(elementText))
        {
            textList.Add(elementText);
        }

        // 遍历子元素
        AutomationElement childElement = walker.GetFirstChild(element);
        while (childElement != null)
        {
            TraverseElements(childElement, textList, walker);
            childElement = walker.GetNextSibling(childElement);
        }
    }
}

步骤3:调用方法

和方案一类似,替换调用逻辑即可:

Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
    if (!string.IsNullOrEmpty(process.MainWindowTitle))
    {
        Console.WriteLine($"=== 窗口标题: {process.MainWindowTitle} ===");
        List<string> content = UIAutomationTextGrabber.GetAllWindowText(process.MainWindowHandle);
        
        if (content.Count > 0)
        {
            Console.WriteLine("窗口内容:");
            foreach (string text in content)
            {
                Console.WriteLine($"- {text}");
            }
        }
        else
        {
            Console.WriteLine("未找到可提取的文本内容");
        }
        Console.WriteLine();
    }
}

总结

  • 如果是老款的Win32应用(比如记事本、系统自带的小工具),用方案一足够。
  • 如果是现代应用(WPF程序、浏览器、UWP应用),优先用方案二,兼容性更强。

内容的提问来源于stack exchange,提问作者Mehmet

火山引擎 最新活动