Search code examples
c#windowscom-interopmicrosoft-ui-automation

How to use FindAll() with the nuget package Interop.UIAutomationClient


I want to use Microsoft UIAutomation, from C#.

I collected some finds I made on the internet and ended up with the following code (using the Nuget package "Interop.UIAutomationClient" version="10.19041.0".)

using System;
using UIA = Interop.UIAutomationClient;

namespace TestUIA
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting...");
            UIA.IUIAutomation NativeAutomation = new UIA.CUIAutomation8();
            var Desktop = NativeAutomation.GetRootElement();
            Console.WriteLine("Destop name is {0}", Desktop.CurrentName);
            Console.WriteLine("Ending...");
        }
    }
}

That works great!

Actual result on my Windows 11 22H2 is:

Now I want to enumerate the Desktop children. I know that I have to use the FindAll method.

Unfortunately, I am only capable of coding the first parameter, as in

    Desktop.FindAll( UIA.TreeScope.TreeScope_Children,

but I don't know how to code the second argument... if I were on C++, I would use IUIAutomation::CreateTrueCondition...

QUESTION: how to pass a "True Condition" to FindAll, in C#, with the Nuget package "Interop.UIAutomationClient"?


Solution

  • You don't need any nuget, you can just reference UIAutomationClient COM dll from Windows as a COM Object:

    enter image description here

    Now, to create the true condition, as you said, you must use the IUIAutomation::CreateTrueCondition method which is implemented on ... CUIAutomation8 (the root class, it's the one which implements IUIAutomation). I've written some extension methods to make that easier:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UIAutomationClient;
    
    internal class Program
    {
        static void Main()
        {
            Console.WriteLine("Starting...");
            var desktop = UIAUtilities.Automation.GetRootElement();
    
            Console.WriteLine("Destop name is {0}", desktop.CurrentName);
            foreach (var element in desktop.EnumerateAll(TreeScope.TreeScope_Children))
            {
                Console.WriteLine(element.CurrentName);
            }
            Console.WriteLine("Ending...");
        }
    }
    
    public static class UIAUtilities
    {
        private static readonly Lazy<CUIAutomation8> _automation = new(() => new CUIAutomation8());
        public static CUIAutomation8 Automation => _automation.Value;
    
        public static IEnumerable<IUIAutomationElement> EnumerateAll(this IUIAutomationElement element, TreeScope scope = TreeScope.TreeScope_Children, IUIAutomationCondition condition = null)
        {
            if (element == null)
                return Enumerable.Empty<IUIAutomationElement>();
    
            condition ??= Automation.CreateTrueCondition();
            return ForEach(element.FindAll(scope, condition));
        }
    
        public static IEnumerable<IUIAutomationElement> ForEach(this IUIAutomationElementArray array)
        {
            if (array == null)
                yield break;
    
            for (var i = 0; i < array.Length; i++)
            {
                var element = array.GetElement(i);
                if (element != null)
                    yield return element;
            }
        }
    }
    

    Here is how the project file (.NET core+) should look like with the COM reference:

    <Project Sdk="Microsoft.NET.Sdk">
      ...
        <ItemGroup>
          <COMReference Include="UIAutomationClient">
            <WrapperTool>tlbimp</WrapperTool>
            <VersionMinor>0</VersionMinor>
            <VersionMajor>1</VersionMajor>
            <Guid>944de083-8fb8-45cf-bcb7-c477acb2f897</Guid>
            <Lcid>0</Lcid>
            <Isolated>false</Isolated>
            <EmbedInteropTypes>true</EmbedInteropTypes>
          </COMReference>
        </ItemGroup>
      ...
    </Project>