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

116 Views Asked by At

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"?

1

There are 1 best solutions below

6
Simon Mourier On BEST ANSWER

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>