0

There is a window from which I want to read a particular text (10.024 in the screenshot below) using WinAPI calls.

This is what this window looks like in Spy++:

To read the value from that label, I first determine the handle of the window:

private TargetWindowInfo FindScrivenerWindow()
{
    IntPtr targetWindowHandle = new IntPtr(0);
    String windowTitle = "";
    Process[] scrivenerProcesses = Process.GetProcessesByName("scrivener");
    if (scrivenerProcesses.Length < 1)
    {
        return null; // Scrivener is not started
    }
    foreach (var handle in EnumerateProcessWindowHandles(scrivenerProcesses.First().Id))
    {
        StringBuilder message = new StringBuilder(1000);
        SendMessage(handle, WM_GETTEXT, message.Capacity, message);
        String curWindowTitle = message.ToString();

        if (curWindowTitle.EndsWith("Project Targets"))
        {
            bool windowVisible = IsWindowVisible(handle);

            if (windowVisible)
            {
                windowTitle = curWindowTitle;
                targetWindowHandle = handle;

                var result = new TargetWindowInfo();

                result.ProjectName = windowTitle.Substring(0, windowTitle.IndexOf(" Project Targets"));
                result.Handle = handle;
                return result;
            }

        }
    }
    return null;
}


var targetWindowInfo = FindScrivenerWindow();

Then I tried to apply this answer to read all strings from the window and hopefully find the desired one (10.024) among them (targetWindowInfo.Handle is the result of FindScrivenerWindow):

List<WinText> windows = new List<WinText>();

//find the "first" window
IntPtr hWnd = targetWindowInfo.Handle;

while (hWnd != IntPtr.Zero)
{
    //find the control window that has the text
    IntPtr hEdit = FindWindowEx(hWnd, IntPtr.Zero, null, null);

    //initialize the buffer.  using a StringBuilder here
    System.Text.StringBuilder sb = new System.Text.StringBuilder(255);  // or length from call with GETTEXTLENGTH

    //get the text from the child control
    int RetVal = SendMessage(hEdit, WM_GETTEXT, sb.Capacity, sb);

    windows.Add(new WinText() { hWnd = hWnd, Text = sb.ToString() });

    //find the next window
    hWnd = FindWindowEx(IntPtr.Zero, hWnd, "scrivener", null);
}

//do something clever
windows.OrderBy(x => x.Text).ToList().ForEach(y => Console.Write("{0} = {1}\n", y.hWnd, y.Text));

Console.Write("\n\nFound {0} window(s).", windows.Count);

windows always has one element and the text is an empty string.

How can I read the value of the label? Is it possible at all given the properties of the window?

Update 1: UI Automation Inspect reports following values for the label:

How found:  Selected from tree...
Name:   ""
BoundingRectangle:  {l:1437 t:455 r:1484 b:475}
IsEnabled:  true
IsOffscreen:    false
IsKeyboardFocusable:    false
HasKeyboardFocus:   false
AccessKey:  ""
ProcessId:  6488
ProviderDescription:    "[pid:6488,providerId:0x0 Main(parent link):Microsoft: MSAA Proxy (unmanaged:uiautomationcore.dll)]"
IsPassword: false
HelpText:   ""
IsDialog:   false
LegacyIAccessible.ChildId:  0
LegacyIAccessible.DefaultAction:    "SetFocus"
LegacyIAccessible.Description:  ""
LegacyIAccessible.Help: ""
LegacyIAccessible.KeyboardShortcut: ""
LegacyIAccessible.Name: ""
LegacyIAccessible.Role: Client (0xA)
LegacyIAccessible.State:    normal (0x0)
LegacyIAccessible.Value:    ""
IsAnnotationPatternAvailable:   false
IsDragPatternAvailable: false
IsDockPatternAvailable: false
IsDropTargetPatternAvailable:   false
IsExpandCollapsePatternAvailable:   false
IsGridItemPatternAvailable: false
IsGridPatternAvailable: false
IsInvokePatternAvailable:   false
IsItemContainerPatternAvailable:    false
IsLegacyIAccessiblePatternAvailable:    true
IsMultipleViewPatternAvailable: false
IsObjectModelPatternAvailable:  false
IsRangeValuePatternAvailable:   false
IsScrollItemPatternAvailable:   false
IsScrollPatternAvailable:   false
IsSelectionItemPatternAvailable:    false
IsSelectionPatternAvailable:    false
IsSpreadsheetItemPatternAvailable:  false
IsSpreadsheetPatternAvailable:  false
IsStylesPatternAvailable:   false
IsSynchronizedInputPatternAvailable:    false
IsTableItemPatternAvailable:    false
IsTablePatternAvailable:    false
IsTextChildPatternAvailable:    false
IsTextEditPatternAvailable: false
IsTextPatternAvailable: false
IsTextPattern2Available:    false
IsTogglePatternAvailable:   false
IsTransformPatternAvailable:    false
IsTransform2PatternAvailable:   false
IsValuePatternAvailable:    false
IsVirtualizedItemPatternAvailable:  false
IsWindowPatternAvailable:   false
IsCustomNavigationPatternAvailable: false
IsSelectionPattern2Available:   false
FirstChild: [null]
LastChild:  [null]
Next:   "" 
Previous:   "" 
Other Props:    Object has no additional properties
Children:   Container has no children
Ancestors:  "short-story-4 Project Targets" Fenster
    "short-story-4 - Scrivener" Fenster
    "Desktop 1" Bereich
    [ No Parent ]
Glory to Russia
  • 17,289
  • 56
  • 182
  • 325
  • 2
    You'll see less surprises when not avoiding [UI Automation](https://learn.microsoft.com/en-us/dotnet/framework/ui-automation/). Use the Inspect.exe tool to quickly verify, that the target window is suitable. – IInspectable Apr 29 '20 at 19:54
  • @IInspectable I pasted the values from Inspect.exe for this particular control. Is it possible to extract the text from this label with the usual WinAPI/UIAutomation means? – Glory to Russia Apr 29 '20 at 20:16
  • 1
    Your 1st Spy++ screenshot shows that the "short-story-4 Project Targets" window doesn't have any child windows, which means it is using window-less controls (ie, they are custom drawn directly onto the parent dialog window), in which case UIAutomation is the only way to access them remotely, if the UI framework even implements support for that (which `Inspect.exe` appears to say is not the case). `QTool` and `QWidget` suggests Qt is the UI framework used, and it does indeed have an option to use window-less controls (https://doc.qt.io/qt-5/qwidget.html#native-widgets-vs-alien-widgets) – Remy Lebeau Apr 29 '20 at 20:16
  • @RemyLebeau Thanks. Is there a way to at least identify this label and get its coordinates (`BoundingRectangle` in the Inspect output)? Then I could make a screenshot of this label. – Glory to Russia Apr 29 '20 at 20:27
  • @DmitriiPisarenko Not if UIAutomation is not supported, no. – Remy Lebeau Apr 29 '20 at 21:53
  • 1
    @RemyLebeau Thanks. If you submit your comment as an answer, I will accept it. – Glory to Russia Apr 30 '20 at 07:10
  • 1
    @DmitriiPisarenko feel free to answer yourself and [Accept Your Own Answers](https://stackoverflow.blog/2009/01/06/accept-your-own-answers/) to end this thread. – Drake Wu May 08 '20 at 09:39

1 Answers1

0

It is impossible to read the text from that field without resorting to exotic and expensive techniques such as DLL injection which is probably an overkill. It is much cheaper to simply make a screenshot of the window, crop the relevant part of the image and run OCR on it.

Glory to Russia
  • 17,289
  • 56
  • 182
  • 325