C++ UIA TextPattern is not found on RichEdit control in Win 7, but C# UIA does find it

David Ching 21 Reputation points
2020-12-17T02:50:21.023+00:00

On Win 7 SP1 with Convenience Update (latest Win 7 that was shipped), my C++ code using CUIAutomation from Windows Automation 3.0 cannot get the TextPattern from the RichEdit control in the built-in WordPad application. However, the equivalent C# code using UIAutomationClient and UIAutomationTypes can.

Better success on Win 10: both the C++ code and the C# code successfully get the TextPattern.

My main project has compatibility issues with another C# UIA application, which went away when I use the C++ code on Win 10. So I really want to use the C++ code on Win 7 also. Does anyone know why the C++ code fails and how to fix it? I am quite surprised that getting a simple TextPattern out of the built-in RichEdit control does not work reliably!

Here's the C# code (much easier to read!):

using System;
using System.Threading;
using System.Windows.Automation;

namespace UIAutomationNET
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Getting element at cursor in 3 seconds...");
            Thread.Sleep(3000);

            var element = AutomationElement.FocusedElement;

            if (element != null)
            {
                var textPatternElement = element.FindFirst(
                    TreeScope.Subtree,
                    new PropertyCondition(AutomationElement.IsTextPatternAvailableProperty, true));

                if (textPatternElement == null)
                    Console.WriteLine("No element supporting TextPattern found.");
                else
                    Console.WriteLine("TextPattern is supported!  :-)");
            }
        }
    }
}

The following C++ code is based on this MSDN Code Gallery sample:

#include <windows.h>
#include <ole2.h>
#include <uiautomation.h>
#include <strsafe.h>

IUIAutomation *_automation;


int _cdecl wmain(_In_ int argc, _In_reads_(argc) WCHAR* argv[])
{
    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(argv);

    // Initialize COM before using UI Automation
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        wprintf(L"CoInitialize failed, HR:0x%08x\n", hr);
    }
    else
    {
        // Use CUIAutomation instead of CUIAutomation8 on Win 7
        hr = CoCreateInstance(__uuidof(CUIAutomation), NULL,
            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_automation));
        if (FAILED(hr))
        {
            wprintf(L"Failed to create a CUIAutomation, HR: 0x%08x\n", hr);
        }
        else
        {
            IUIAutomationElement *element = NULL;
            wprintf( L"Getting element at cursor in 3 seconds...\n" );
            Sleep(3000);

            POINT pt;
            GetCursorPos(&pt);
            hr = _automation->ElementFromPoint(pt, &element);
            if (FAILED(hr))
            {
                wprintf( L"Failed to ElementFromPoint, HR: 0x%08x\n\n", hr );
            }

            if (SUCCEEDED(hr) && element != NULL)
            {
                IUIAutomationElement *textElement = NULL;

                // Create a condition that will be true for anything that supports Text Pattern
                // Use UIA_IsTextPatternAvailablePropertyId instead of UIA_IsTextPattern2AvailablePropertyId on Win 7
                IUIAutomationCondition* textPatternCondition;
                VARIANT trueVar;
                trueVar.vt = VT_BOOL;
                trueVar.boolVal = VARIANT_TRUE;
                hr = _automation->CreatePropertyCondition(UIA_IsTextPatternAvailablePropertyId, trueVar, &textPatternCondition);

                if (FAILED(hr))
                {
                    wprintf(L"Failed to CreatePropertyCondition, HR: 0x%08x\n", hr);
                }
                else
                {
                    // Actually do the search
                    hr = element->FindFirst(TreeScope_Subtree, textPatternCondition, &textElement);
                    if (FAILED(hr))
                    {
                        wprintf(L"FindFirst failed, HR: 0x%08x\n", hr);
                    }
                    else if (textElement == NULL)
                    {
                        wprintf(L"No element supporting TextPattern found.\n");
                        hr = E_FAIL;
                    }
                    else
                    {
                        wprintf(L"TextPattern is supported!  :-)\n");
                    }
                    textPatternCondition->Release();
                }
            }
            _automation->Release();
        }
        CoUninitialize();
    }

    return 0;
}
Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,412 questions
{count} votes

Accepted answer
  1. WithinRafael 326 Reputation points
    2020-12-17T09:50:05.633+00:00

    Hi @David Ching ,

    The managed implementation of UIAutomation contains client-side automation providers that act as proxies between alien controls like the RichEdit in Wordpad and expose patterns such as TextPattern. You can access the full list via UIAutomationClientsideProviders.UIAutomationClientSideProviders.ClientSideProviderDescriptionTable if you'd like.

    Looking around, it does not appear these client-side providers are available for your use in an unmanaged implementation. (You may be able to mix the two with a bunch of interop glue, perhaps.) Alternatively, you could re-implement the managed client-side provider.

    In a new, separate question, we could explore the managed compatibility issues you ran into, if you'd like.

    1 person found this answer helpful.
    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Rita Han - MSFT 2,161 Reputation points
    2020-12-17T07:14:54.64+00:00

    (In addition to last comment, it is too long to post as a comment) @David Ching The following is an example for identifying the issue on Windows 7:

    		hr = _automation->ElementFromPoint(pt, &element);  
    		if (FAILED(hr))  
    		{  
    			wprintf(L"Failed to ElementFromPoint, HR: 0x%08x\n\n", hr);  
    		}  
    
    		if (element != NULL)  
    		{  
    			BSTR clsName;  
    			hr = element->get_CurrentClassName(&clsName);  
    			if (FAILED(hr))  
    				wprintf(L"get_CurrentClassName fails, HR: 0x%08x\n", hr);  
    			else  
    				wprintf(L"CurrentClassName is %s\n", clsName);  
    
    			IUIAutomationTextPattern* textPattern = NULL;  
    			// GetTextPattern  
    			hr = element->GetCurrentPatternAs(UIA_TextPatternId, IID_PPV_ARGS(&textPattern));  
    			if (FAILED(hr))  
    			{  
    				wprintf(L"Failed to Get Text Pattern, HR: 0x%08x\n", hr);  
    			}  
    			else if (textPattern == NULL)  
    			{  
    				wprintf(L"Element does not actually support Text Pattern 2\n");  
    				hr = E_FAIL;  
    			}  
    			else  
    				wprintf(L"IUIAutomationTextPattern2 is supported!\n");  
    		}  
    

    If the answer is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

  2. David Ching 21 Reputation points
    2020-12-17T19:01:54.96+00:00

    Thanks so much @WithinRafael ! I really appreciate the background and suggestions in your answer. I had stumbled on the code for the UIAutomation (managed) RichEdit client-side automation provider before, but hadn't realized that it was what was providing the TextPattern to managed code, and thus unmanaged applications would not have it. This is verified by both INSPECT.exe and Accessibility Insights (UIA utilities that use Windows Automation 3.0) also show IsTextPatternAvailable to be False.

    Looking through it's implementation in WindowsRichEdit.cs code in the link you provided, I think I could translate that into C++. But how would I cause this unmanaged ITextProvider implementation to be used by the IUIAutomation object in my C++ client code?

    Also, I am curious, where do you look for the available client-side providers for unmanaged code?

    A bit more about my situation. Our UI automation client is a C# application. It initially worked well with RichEdit, thanks to the built-in managed automation provider. However, it started experiencing an incompatibility with a 3rd party WPF app also doing UIA on the same RichEdit control at the same time. Both our apps monitor for changes in the RichEdit text and then try to get the RichEdit AutomationElement, and sometimes that fails. But only sometimes. ;)

    I found that switching our C# app from UIAutomation to UIAComWrapper (which as I understand is a wrapper around the CUIAutomation used in this C++ code above) makes our app work 100%. The 3rd party app (which presumably still uses UIAutomation) continues to fail sometimes. Point for UIAComWrapper (and CUIAutomation). Another point is that we found UIAComWrapper produced TextPattern reliably in Internet Explorer whereas UIAutomation didn't. So it just seems UIAComWrapper (and thus CUIAutomation) work better in general than UIAutomation.

    I thought there might be a bug in UIAComWrapper, so for these tests, I skipped using it and instead wrote directly to CUIAutomation. But since that also fails, it seems UIAComWrapper is fine.

    Knowing that my goal is ultimately to use the C# UIAComWrapper (and not C++ CUIAutomation), is it any easier to implement the RichEdit client-side automation provider?

    Thank you again!