How come I can’t find a Text pattern for Notepad, when Inspect tells me it’s there?


An interesting question arose recently from a dev building a C++ UIA client app which accesses text from Notepad. The Inspect SDK tools reports that Notepad supports the Text pattern, (as shown in the screenshot below,) but when the dev tries to access the Text pattern from Notepad, he's told that Notepad doesn't support it. So what's going on?

 

Figure 1: The Inspect SDK tool reporting that a document-related element in the Notepad UI supports the TextPattern and TextPattern2 interfaces.

 

The most likely reason for this apparent discrepancy relates to the object created when the client code accesses a IUIAutomation interface. It's natural to want to do this in your code...

    pUIAutomation;
    hr = CoCreateInstance(CLSID_CUIAutomation, NULL,
        CLSCTX_INPROC_SERVER,  IID_PPV_ARGS(&pUIAutomation));
    if (SUCCEEDED(hr))
    {
        ...

 

While that works, it's really important to note that having done that, some of the work that UIA will now do for you is done by a CLSID_CUIAutomation object. The CLSID_CUIAutomation object was added in Windows 7 when the native Windows UIA API first shipped. In Windows 8, a CLSID_CUIAutomation8 object was added to the Windows UIA API. By using a CLSID_CUIAutomation8 object, calls to UIA became more robust because the CLSID_CUIAutomation8 object takes a lot of action to avoid delays when UIA providers become unresponsive.

And in addition to making UIA more robust, support for a few more things came along in Windows 8. One of those things was support for the Text pattern in Edit controls. In Windows 7, Edit controls didn't support the Text pattern. So in order to be able to access the Text pattern from an Edit control, you need to instantiate a CLSID_CUIAutomation8 object. Given that Notepad hosts a big Edit control, that's what you'll need to do in order to access Notepad's Text pattern.

I've just written the code below, and I can get the text from Notepad fine through the Text pattern.

 

Figure 2: Test app accessing the text shown in Notepad through the Text pattern.

 

I always use CLSID_CUIAutomation8 these days when starting up UIA.

Thanks,

Guy

 

void DoUIAStuff()
{
    HRESULT hr = S_OK;
    

    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        // Be sure to get an interface to a CLSID_CUIAutomation8 object rather than to a CLSID_CUIAutomation
        // object here. When using the CLSID_CUIAutomation8 object, Notepad is represented through UIA as a
        // Document control. If a CLSID_CUIAutomation object is used, the Notepad is represented as an Edit
        // control. (Note that for this test, I'm not interested in IUIAutomation2 or IUIAutomation3.)

    
        IUIAutomation* pUIAutomation;
        hr = CoCreateInstance(CLSID_CUIAutomation8, NULL,
            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pUIAutomation));
        if (SUCCEEDED(hr))
        {
            IUIAutomationTextPattern* pTextPattern = NULL;
    

            // For this test, assume we're intestered in the first Notepad window we find.

    

            HWND hWndNotepad = FindWindow(L"Notepad", NULL);
            if (hWndNotepad != NULL)
            {
                // Get the UIA element associated with the top-level Notepad window.

    

                IUIAutomationElement* pElementNotepad;
                hr = pUIAutomation->ElementFromHandle(hWndNotepad, &pElementNotepad);
                if (SUCCEEDED(hr) && (pElementNotepad != NULL))
                {
                    // Now that we have the Notepad window element, find the child element that supports the
                    // Text pattern. We can check exactly what to look for by pointing the Inspect SDK tool
                    // to the Notepad window, and looking at the various properties of the element. For this
                    // test, we'll look for the first child whose control type is Document.

    

                    IUIAutomationCondition* pConditionDocument = NULL;

    

                    VARIANT varProp;
                    varProp.vt = VT_I4;
                    varProp.lVal = UIA_DocumentControlTypeId;

    

                    // When we find the element we're interested in, get as much data as we can cached with the
                    // element beneath the call that finds it. This means we can avoid some cross-process calls
                    // later when we need to examine some properties on the element.

    

                    IUIAutomationCacheRequest* pCacheRequest = NULL;

    

                    // Note that if we didn't know exactly which element supported the Text pattern, we could
                    // update the condition to say that only elements which do support the Text pattern should
                    // be returned. But in the case of this test, we know exactly which element we're after.
                    // (In fact we could just use a IUIAutomationTreeWalker to get the first child of the hwnd
                    // element if we wanted to.)

    
                    hr = pUIAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId,
                        varProp, &pConditionDocument);
                    if (SUCCEEDED(hr))
                    {
                        hr = pUIAutomation->CreateCacheRequest(&pCacheRequest);
                        if (SUCCEEDED(hr))
                        {
                            hr = pCacheRequest->AddProperty(UIA_IsTextPatternAvailablePropertyId);
                            if (SUCCEEDED(hr))
                            {
                                hr = pCacheRequest->AddPattern(UIA_TextPatternId);
                            }
                        }
                    }
    

                    if (SUCCEEDED(hr))
                    {
                        // Ok, let's try to find the element that supports the Text pattern.

    
                        IUIAutomationElement* pElementDocument;
                        hr = pElementNotepad->FindFirstBuildCache(TreeScope_Children,
                            pConditionDocument, pCacheRequest, &pElementDocument);
                        if (SUCCEEDED(hr) && (pElementDocument != NULL))
                        {
                            // Make a check that the element claims to support the Text pattern.
    
                            VARIANT varBool;
                            hr = pElementDocument->GetCachedPropertyValue(UIA_IsTextPatternAvailablePropertyId,
                                &varBool);
                            if (SUCCEEDED(hr) && (varBool.vt == VT_BOOL) && (varBool.boolVal))
                            {
                                // Now get an interface to the Text pattern.
    
                                hr = pElementDocument->GetCachedPatternAs(UIA_TextPatternId,
                                    IID_PPV_ARGS(&pTextPattern));
                            }
    

                            pElementDocument->Release();
                        }
                    }

    

                    if (pConditionDocument != NULL)
                    {
                        pConditionDocument->Release();
                        pConditionDocument = NULL;
                    }

    

                    if (pCacheRequest != NULL)
                    {
                        pCacheRequest->Release();
                        pCacheRequest = NULL;
                    }

    

                    pElementNotepad->Release();
                    pElementNotepad = NULL;
                }
            }

    

            // Having done all that, do we now have a Text pattern with which to access Notepad's text?

    

            if (SUCCEEDED(hr) && (pTextPattern != NULL))
            {
                // In this test, get a TextRange for the entire text.

    

                IUIAutomationTextRange* pTextRange;
                hr = pTextPattern->get_DocumentRange(&pTextRange);
                if (SUCCEEDED(hr) && (pTextRange != NULL))
                {
                    BSTR bstrNotepadText;
                    hr = pTextRange->GetText(-1, &bstrNotepadText);
                    if (SUCCEEDED(hr)&& (bstrNotepadText != NULL))
                    {
                        MessageBox(NULL, bstrNotepadText, L"Text from Notepad", MB_OK);

    

                        SysFreeString(bstrNotepadText);
                    }

    

                    pTextRange->Release();
                    pTextRange = NULL;
                }

    

                pTextPattern->Release();
                pTextPattern = NULL;
            }

    

            pUIAutomation->Release();
            pUIAutomation = NULL;
        }

    
        CoUninitialize();
    }
}
    

Comments (1)

  1. A follow-up discussion with the dev who raised the question led to an important point that I'd not focused on. I said above that I only use the CLSID_CUIAutomation8 class id when getting an IUIAutomation interface. That's just hunky-dory if my code's running on Windows 8/8.1 or Windows 10 devices, but it's not going to work if it's running on Windows 7.

    So if my code's going to be running on Windows 7, 8, 8.1 and 10, I'll try using the CLSID_CUIAutomation8 class id first, and if I fail to get an IUIAutomation interface with that, then I'll fall back to using a CLSID_CUIAutomation. While UIA on Windows 7 is more vulnerable to delays when it encounters unresponsive providers, (and you can't do cool things like interact with Notepad through a Text pattern,) you can still do all sorts of great UI automation with it.

    Guy

Skip to main content