Obtaining UI Automation Elements

This topic describes various ways to obtain IUIAutomationElement interfaces for user interface (UI) elements. It contains the following sections:

  • Root Element
  • Conditions
  • Search Scope
  • Finding a Known Element
  • Finding Elements in a Subtree
  • Walking a Subtree
  • Other Ways to Retrieve an Element
    • From an Event
    • From a Point
    • From a Window Handle
    • From the Focused Control

Root Element

Although elements can be retrieved directly by using methods, such as IUIAutomation::GetFocusedElement, some client applications require a view of the hierarchical structure of elements, known as the UI Automation tree. The root element of this hierarchy is the desktop. You can obtain this element by using the IUIAutomation::GetRootElement method or the IUIAutomation::GetRootElementBuildCache method. Both these methods retrieve an IUIAutomationElement interface pointer. You can search for descendant elements by using methods, such as IUIAutomationElement::FindFirst and IUIAutomationElement::FindAll.

Note   In general, you should try to obtain only direct children of the root element. A search for descendants may iterate through hundreds or thousands of elements. If you are attempting to obtain a specific element at a lower level, you should start your search from the application window or from a container at a lower level.

Conditions

For most techniques you use to retrieve UI Automation elements, you must specify a condition. A condition is a set of criteria that defines the elements that you want to retrieve. A condition is represented by the IUIAutomationCondition interface.

The simplest condition is the true condition, which is a predefined object that specifies that all elements in the search scope are to be returned. The false condition is the inverse of the true condition and is less useful, because it would prevent any elements from being found. You can obtain an interface to the true condition by using IUIAutomation::CreateTrueCondition.

Three other predefined conditions, available as properties on the IUIAutomation object, can be used alone or in combination with other conditions: IUIAutomation::ContentViewCondition, IUIAutomation::ControlViewCondition, and IUIAutomation::RawViewCondition. IUIAutomation::RawViewCondition, used by itself, is equivalent to the true condition, because it does not filter elements by the IUIAutomationElement::CurrentIsControlElement or IUIAutomationElement::CurrentIsContentElement properties.

Other conditions are built from condition objects, each of which specifies a property value. For example, a property condition might specify that the element is enabled or that it supports a certain control pattern.

Conditions that use Boolean logic can be combined by calling IUIAutomation::CreateAndCondition, IUIAutomation::CreateOrCondition, IUIAutomation::CreateNotCondition, and related methods.

Search Scope

Searches that are performed by using IUIAutomationElement::FindFirst or IUIAutomationElement::FindAll must have a scope and a starting place.

Note  Any commentary about these two methods also applies to IUIAutomationElement::FindFirstBuildCache and IUIAutomationElement::FindAllBuildCache.

The scope defines the space around the starting place that is to be searched. This might include the element itself, its siblings, its parent, its ancestors, its immediate children, and its descendants.

The scope of a search is defined by a bitwise combination of values from the TreeScope enumerated type.

Finding a Known Element

To find a known element that is identified by name, automation ID, or some other property or combination of properties, it is easiest to use the IUIAutomationElement::FindFirst method. If the element sought is an application window, the starting point of the search can be the root element.

This way of finding UI Automation elements is most useful in automated testing scenarios.

The following example function uses the name property to retrieve a UI Automation element that is a child of the desktop.

IUIAutomationElement* GetTopLevelWindowByName(LPWSTR windowName)
{
    if (windowName == NULL)
    {
        return NULL;
    }

    VARIANT varProp;
    varProp.vt = VT_BSTR;
    varProp.bstrVal = SysAllocString(windowName);
    if (varProp.bstrVal == NULL)
    {
        return NULL;
    }

    IUIAutomationElement* pRoot = NULL;
    IUIAutomationElement* pFound = NULL;

    // Get the desktop element. 
    HRESULT hr = g_pAutomation->GetRootElement(&pRoot);
    if (FAILED(hr) || pRoot == NULL)
        goto cleanup;

    // Get a top-level element by name, such as "Program Manager"
    IUIAutomationCondition* pCondition;
    hr = g_pAutomation->CreatePropertyCondition(UIA_NamePropertyId, varProp, &pCondition);
    if (FAILED(hr))
        goto cleanup;

    pRoot->FindFirst(TreeScope_Children, pCondition, &pFound);

cleanup:
    if (pRoot != NULL)
        pRoot->Release();

    if (pCondition != NULL)
        pCondition->Release();

    VariantClear(&varProp);
    return pFound;
}

Finding Elements in a Subtree

To find all elements that meet specific criteria and are related to a known element, you can call IUIAutomationElement::FindAll on the known element. For example, use this method to retrieve list items or menu items from a list or menu, or to identify all controls in a dialog box.

The following example function returns a collection of all enabled buttons that are children of the specified element.

IUIAutomationElementArray* GetEnabledButtons(IUIAutomationElement* pParent)
{
    if (pParent == NULL)
    {
        return NULL;
    }
    IUIAutomationCondition* pButtonCondition = NULL;
    IUIAutomationCondition* pEnabledCondition = NULL;
    IUIAutomationCondition* pCombinedCondition = NULL;
    IUIAutomationElementArray* pFound = NULL;

    // Create a property condition for the button control type.
    VARIANT varProp;
    varProp.vt = VT_I4;
    varProp.lVal = UIA_ButtonControlTypeId;
    g_pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, varProp, &pButtonCondition);
    if (pButtonCondition == NULL)
        goto cleanup;

    // Create a property condition for the enabled property.
    varProp.vt = VT_BOOL;
    varProp.boolVal = VARIANT_TRUE;
    g_pAutomation->CreatePropertyCondition(UIA_IsEnabledPropertyId, varProp, &pEnabledCondition);
    if (pEnabledCondition == NULL)
        goto cleanup;

    // Combine the conditions.
    g_pAutomation->CreateAndCondition(pButtonCondition, pEnabledCondition, &pCombinedCondition);
    if (pCombinedCondition == NULL)
        goto cleanup;

    // Find the matching elements. Note that if the scope is changed to TreeScope_Descendants, 
    // system buttons on the caption bar will be found as well.
    pParent->FindAll(TreeScope_Children, pCombinedCondition, &pFound);

cleanup:
    if (pButtonCondition != NULL)
        pButtonCondition->Release();

    if (pEnabledCondition != NULL) 
        pEnabledCondition->Release();
    
    if (pCombinedCondition != NULL)
        pCombinedCondition->Release();

    return pFound;
}

Walking a Subtree

If you have no prior knowledge of the applications that your client may be used with, you can construct a subtree of all elements of interest by using IUIAutomationTreeWalker. Your client might do this, for example, in response to a focus-changed event; that is, when an application or control receives input focus, the Microsoft UI Automation client examines children and perhaps all descendants of the focused element.

Be aware that walking the UI Automation tree is expensive in terms of performance. Walk the tree only when it is not feasible to use the IUIAutomationElement::FindFirst, FindAll, or BuildUpdatedCache methods.

You can define your own tree walker by passing a custom condition to IUIAutomation::CreateTreeWalker, or you can use one of the following predefined objects that are defined as properties of the base IUIAutomation.

Object Purpose
IUIAutomation::ContentViewWalker Finds only elements whose IUIAutomationElement::CurrentIsContentElement property is true.
IUIAutomation::ControlViewWalker Finds only elements whose IUIAutomationElement::CurrentIsControlElement property is true.
IUIAutomation::RawViewWalker Finds all elements.

After you obtain an IUIAutomationTreeWalker, call the Get methods to navigate elements of the subtree, passing in the element from which to start walking.

The following code example is a recursive function that walks through all the descendants of a UI Automation element and displays their control types in a hierarchical list.

// CAUTION: Do not pass in the root (desktop) element. Traversing the entire subtree
// of the desktop could take a very long time and even lead to a stack overflow.
void ListDescendants(IUIAutomationElement* pParent, int indent)
{
    if (pParent == NULL)
        return;

    IUIAutomationTreeWalker* pControlWalker = NULL;
    IUIAutomationElement* pNode = NULL;

    g_pAutomation->get_ControlViewWalker(&pControlWalker);
    if (pControlWalker == NULL)
        goto cleanup;

    pControlWalker->GetFirstChildElement(pParent, &pNode);
    if (pNode == NULL)
        goto cleanup;

    while (pNode)
    {
        BSTR desc;
        pNode->get_CurrentLocalizedControlType(&desc);
        for (int x = 0; x <= indent; x++)
        {
            std::wcout << L"   ";
        }
        std::wcout << desc << L"\n";
        SysFreeString(desc);

        ListDescendants(pNode, indent+1);
        IUIAutomationElement* pNext;
        pControlWalker->GetNextSiblingElement(pNode, &pNext);
        pNode->Release();
        pNode = pNext;
    }

cleanup:
    if (pControlWalker != NULL)
        pControlWalker->Release();

    if (pNode != NULL)
        pNode->Release();

    return;
}

Another way in which IUIAutomationTreeWalker can be used is to identify the ancestors of an element. For instance, by walking up the tree you can identify the parent window of a control, as in the following example code.

IUIAutomationElement* GetContainingWindow(IUIAutomationElement* pChild)
{

    if (pChild == NULL)
        return NULL;

    IUIAutomationElement* pDesktop = NULL;
    HRESULT hr = g_pAutomation->GetRootElement(&pDesktop);
    if (FAILED(hr))
        return NULL;

    BOOL same;
    g_pAutomation->CompareElements(pChild, pDesktop, &same);
    if (same)
    {
        pDesktop->Release();
        return NULL; // No parent, so return NULL.
    }

    IUIAutomationElement* pParent = NULL;
    IUIAutomationElement* pNode = pChild;

    // Create the treewalker.
    IUIAutomationTreeWalker* pWalker = NULL;
    g_pAutomation->get_ControlViewWalker(&pWalker);
    if (pWalker == NULL)
        goto cleanup;

    // Walk up the tree.
    while (TRUE)
    {
        hr = pWalker->GetParentElement(pNode, &pParent);
        if (FAILED(hr) || pParent == NULL)
        {
            break;
        }
        g_pAutomation->CompareElements(pParent, pDesktop, &same);
        if (same)
        {
            pDesktop->Release();
            pParent->Release();
            pWalker->Release();
            // Reached desktop, so return next element below it.
            return pNode;
        }
        if (pNode != pChild) // Don't release the in-param.
            pNode->Release();
        pNode = pParent;
    }

cleanup:
    if ((pNode != NULL) && (pNode != pChild)) 
        pNode->Release();

    if (pDesktop != NULL)
        pDesktop->Release();

    if (pWalker != NULL)
        pWalker->Release();

    if (pParent != NULL)
        pParent->Release();

    return NULL;  
}

The IUIAutomationTreeWalker::Normalize method can be used for navigating to an element in the subtree from another element that is not part of the view. For example, suppose you create a view of a subtree by using IUIAutomation::ContentViewWalker. Your application receives notification that a scroll bar has received the input focus. Because a scroll bar is not a content element, it is not present in your view of the subtree. However, you can pass the IUIAutomationElement that represents the scroll bar to IUIAutomationTreeWalker::Normalize and retrieve the nearest ancestor that is in the content view.

Other Ways to Retrieve an Element

In addition to searches and navigation, you can retrieve an IUIAutomationElement in the following ways.

From an Event

When your application receives a UI Automation event, the source object passed to your event handler is represented by an IUIAutomationElement. For example, if you subscribe to focus-changed events, the source passed to your IUIAutomationFocusChangedEventHandler is the element that received the focus. For more information, see UI Automation Events for Clients.

From a Point

To retrieve an IUIAutomationElement from screen coordinates, for example, a cursor position, use the IUIAutomation::ElementFromPoint method.

From a Window Handle

To retrieve an IUIAutomationElement from an HWND, use the IUIAutomation::ElementFromHandle method.

From the Focused Control

To retrieve an IUIAutomationElement that represents the focused control, use the IUIAutomation::GetFocusedElement method.