Getting the Handle to a Deeply Nested Control using C# P/Invoke

Recently I was working with the C# P/Invoke mechanism to perform some low-level UI automation on a desktop application. The heart of most of the tasks is to get a handle to a control window, such as a text box or a button, and then send a Windows message, such as a mouse-click, to the control. The application I was working with had some very deeply nested control windows. I decided to write a function that that would get the handle to a deeply nested control. See the image below. I have a dummy WinForm app. The target is the RichTextBox inside the colored panel controls. If you compare the console app demo and the Spy++ tool you’ll see that the demo did in fact find the handle to the RichTextBox control.

The function, which I named GetChildWindowHandle is:

static IntPtr GetDeepChildHandle(IntPtr hwndParent,
 string captionOrText, int sanityCount, int maxChildrenPerParent)
{
  Stack stack = new Stack();
  stack.Push(hwndParent);
  int sanity = 0;
  while (stack.Count > 0 && sanity < sanityCount)
  {
    IntPtr curr = stack.Pop();
    string currCaption = GetControlText(curr);
    if (currCaption == captionOrText)
      return curr;

    List allChildren =
      GetAllChildrenWindowHandles(curr, maxChildrenPerParent);
    if (allChildren.Count > 0) // not strictly required
    {
      for (int i = 0; i < allChildren.Count; ++i)
        stack.Push(allChildren[i]);
    }
    ++sanityCount;
  }
  return IntPtr.Zero; // window was not found
}

The idea is to traverse the controls on the application, checking each control's caption to see if we hit the target control. In pseudo code:

get app handle
push app handle on stack
while stack not empty
  pop stack into curr handle
  if text of curr is target return curr
  get all child handles of curr
  push all child handles on stack
end loop
return handle-not-found

The sanityCount parameter prevents an infinite loop or searching too many controls. The maxChildrenPerParent parameter also limits window craziness. Because I use a stack, I do a depth-first search. Using a queue would perform a breadth-first search.

Actually, the most difficult parts of the entire process are the helper functions that get all child handles, and get a control's text or caption. I described those helpers in my previous two blogs. In general I most enjoy working with algorithms and data, but occasional trips into P/Invoke land are fun.

GetDeeplyNestedControlHandle

About these ads
This entry was posted in Software Test Automation. Bookmark the permalink.