【问题标题】:Scrape text from textbox in Windows application从 Windows 应用程序的文本框中抓取文本
【发布时间】:2012-01-12 13:21:56
【问题描述】:

是否可以从包含在单独可执行文件中的文本框中抓取文本?我有一个具有调试窗口的应用程序。调试窗口生成详细日志。但是,日志永远不会保存在任何地方,只能在应用程序中查看。如果应用程序产生异常,我想给自己发电子邮件,知道已经产生了异常,这样我就可以跳进去检查一下。还有一个复制文本框的按钮,所以我正在考虑使用 Spy++ 来获取命令信息。但是,我不知道从那里去哪里。任何指针都非常感谢。

我更喜欢在 .NET 中使用 C#,但如果我需要使用 C++,我会。

更新

基于 cmets,我尝试执行以下操作:

Private Declare Function GETWINDOWTEXT Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String, ByVal cch As Integer) As Integer
Private Declare Function SetForegroundWindow Lib "user32.dll" (ByVal hwnd As Integer) As Integer
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindow As String) As IntPtr
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer
Private Declare Ansi Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As String) As Integer
Private Const WM_GETTEXT As Short = &HDS
Private Const WM_GETTEXTLENGTH As Short = &HES

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    Dim hwnd As Integer = FindWindowEx(0, 0, "MyAppForm", "Hello World")

    If Not hwnd = 0 Then
        SetForegroundWindow(hwnd)

        'Dim LabelEx As Integer = FindWindowEx()
        Dim TextBoxEx As Integer = FindWindowEx(hwnd, 0, "MyAppTextBox", vbNullString)
        Dim txtLength As Long = SendMessage(TextBoxEx, WM_GETTEXTLENGTH, CInt(0), CInt(0)) + 1
        Dim txtBuff As String = Space(txtLength)
        Dim txtValue As Long = SendMessage(TextBoxEx, WM_GETTEXT, txtLength, txtBuff)

        MsgBox(txtBuff)
    End If
End Sub

但是,我似乎找不到文本框控件的句柄。当我枚举所有窗口时,我只看到一个 TextBox 对象,但在整个枚举过程中我多次看到父对象。如何获得指向窗口内控件的指针?

更新 2:

我上传了一个示例 Windows 应用程序,以显示我尝试访问的应用程序类型。除了文本框之外,我还试图获取两个标签的值。文本框是最重要的。示例 Win 应用在这里:http://www.mediafire.com/file/172r2xapj7p4f2f/StatusSimulator.zip

【问题讨论】:

  • 一个问题:你怎么知道发生了异常?
  • 您的独立可执行文件是 .Net 应用程序吗?
  • 你试过Ctrl A -> Ctrl C
  • 找到windows hwnd然后发送wm_gettext,看看@stackoverflow.com/questions/7732705/…说明原理
  • @AlexK,你或许应该把这个作为答案。

标签: c# c++ winforms winapi screen-scraping


【解决方案1】:

当您使用 WM_GETTEXT 消息时,您提供了一个指向接收文本的缓冲区的指针。如果您将此消息发送到另一个进程中的窗口,那么您提供的指针将在您的进程地址空间中,而不是在其他进程中。

我做过类似的事情(在另一个进程中抓取窗口),我所做的工作是使用 DLL 注入。基本上,您使用 SetWindowsHook 并在 DLL 中提供回调。操作系统然后将 DLL 加载到其他进程中,您可以确定何时加载到所需的目标进程中。此时您的代码在其他进程中,您可以获得窗口文本。

然后就是将其返回到您的应用程序的问题。我自己使用共享内存块来完成。不过,也许有一种更简单的方法来完成这一切,但这在过去对我有用。

【讨论】:

  • 我并不关心窗口文本,而是关心窗口中包含的控件。例如,如果窗口有 3 个文本框,我可能会关注其中的 1 个或 2 个。我如何阅读它们的内容?
  • @Nerdtron:Windows 将对系统消息执行封送处理(0 到 WM_USER-1)。请参阅msdn.microsoft.com/en-us/library/windows/desktop/…blogs.msdn.com/b/oldnewthing/archive/2003/08/21/54675.aspx,这不是问题
  • @shf301 很有趣。对于我正在做我描述的特定情况,我试图从树控件中读取项目文本,发现它不能跨进程边界工作。所以也许这不适用于树控制消息传递。感谢您的链接!
  • @JasonN.Gaylord 查看 shf301 的链接。对于某些消息,您似乎不必像我所说的那样参与其中。
  • @Nerdtron - 你们失去了我。我想我不能用长词。因此,我尝试使用以下内容(再次在 VB 中使用 VB 启动快速示例应用程序): Dim szBuf As Char() Dim status As Integer = GETWINDOWTEXT(hwnd, szBuf, 1000) 但这并没有得到任何东西. szBuf 的值为 Nothing,status 的值为 26。它并没有真正帮助我获取文本框文本。
【解决方案2】:

文本抓取由可访问性界面完成。对于托管代码,您可以使用 System.Windows.Automation 命名空间中的类。如果你已经有一个窗口句柄,那么提取文本很简单:

AutomationElement.FromHandle(hwnd)
                 .GetCurrentPropertyValue(ValuePattern.ValueProperty) as string;

(有点困惑,因为问题被标记为 C# 并且您要求 C# 解决方案,但您的代码示例是在 VB 中。)

【讨论】:

  • 对不起。刚刚从别处提取了一个 VB 示例。
  • 你指的“它”是什么?执行抓取的程序在托管代码中,因为您是用 C# 编写的。被抓取的程序是托管的还是非托管的都没有关系。 (原始的可访问性接口是非托管的,但 System.Windows.Automation 命名空间提供了一个方便的托管包装器。)
  • 我在使用时得到一个空结果: Dim hwnd As Integer = FindWindowEx(0, 0, "MyAppForm", vbNullString) If Not hwnd = 0 Then Dim TextBoxEx As Integer = FindWindowEx(hwnd , 0, "MyTextBox", vbNullString) Dim value As String = AutomationElement.FromHandle(TextBoxEx).GetCurrentPropertyValue(ValuePattern.ValueProperty).ToString()
  • 是否有工具可以让我枚举句柄以确保获得正确的窗口句柄?我尝试过使用 VB6 进行枚举,并看到大约 100 个类,其中包括几个相同的类。这就是为什么我认为我得到了不正确的结果。
  • 您可以使用 Spy++ 查看窗口层次结构并使用它来确定如何获取窗口。或者您可以使用AutomationElement.FindFirst 并传递一个描述您正在寻找的目标的条件。见Obtaining UI Automation Elements; “在子树中查找元素”部分看起来像您所追求的。
【解决方案3】:

GetWindowText 的 P/Invoke 定义不正确。使用 P/Invoke String 类型只会编组,而不是编组,这就是为什么您没有收到任何文本的原因。如果您将 lpString 参数更改为 StringBuffer,它将起作用。

请注意,您还需要将 StringBuffer 的容量设置为 1000,然后再将其传递给 GetWindowText,因为它需要将分配的缓冲区传递给它 - 它不会调整 StringBuffer 本身的大小。

请参阅定义和示例以及pinvoke.net

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindowText(ByVal hwnd As IntPtr, ByVal lpString As StringBuilder, ByVal cch As Integer) As Integer
End Function

Private Declare Function SetForegroundWindow Lib "user32.dll" (ByVal hwnd As Integer) 

As Integer
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindow As String) As IntPtr
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer
Private Declare Ansi Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As String) As Integer
Private Const WM_GETTEXT As Short = &HDS
Private Const WM_GETTEXTLENGTH As Short = &HES

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    Dim hwnd As Integer = FindWindowEx(0, 0, "MyAppForm", "Hello World")

    If Not hwnd = 0 Then
        SetForegroundWindow(hwnd)

        'Dim LabelEx As Integer = FindWindowEx()
        Dim TextBoxEx As Integer = FindWindowEx(hwnd, 0, "MyAppTextBox", vbNullString)
        Dim txtLength As Long = SendMessage(TextBoxEx, WM_GETTEXTLENGTH, CInt(0), CInt(0)) + 1
        Dim txtBuff As New System.Text.StringBuilder(txtLength + 1)
        GetWindowText(hWnd, txtBuff , txtBuff .Capacity)
        Dim txtValue As Long = SendMessage(TextBoxEx, WM_GETTEXT, txtLength, txtBuff)
        MsgBox(txtBuff.ToString())
    End If
End Sub

【讨论】:

  • 有没有一种简单的方法来枚举容器中的处理程序?
  • 如果你有应用程序的窗口句柄,枚举窗口的方法是用户EnumChildWindowsEnumWindows 用于所有顶级窗口,EnumThreadWindows 用于枚举线程拥有的所有窗口。查看 MSDN 中列出的 Window 函数,看看有什么可能,然后查看 pinvoke.net 以获取它们的定义。 msdn.microsoft.com/en-us/library/windows/desktop/…
  • 那么,我应该可以按名称(或标题文本)抓取父窗口,然后枚举子窗口以找到标签和文本框的 InPtr 值,对吗?
  • @JasonN.Gaylord - 是的,这是正确的。您将需要递归枚举所有子窗口以查看所有子窗口。也就是说,您需要在来自 EnumChildWindows 的回调中收到的句柄上调用 EnumChildWindows
猜你喜欢
  • 2011-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多