如何在 C# 中以高性能获取 UI 树

我现在通过 AutomationElementIUIAutomationElement 在 C# 中获取 UI 树。但是,两者的性能都很差。我写了一个DFS算法来获取窗口的UI Tree中的结束元素,搜索需要大约5秒,这是不可接受的。此外,我自己实现了一个 ThreadPool 以加快该过程,但几乎没有任何帮助。以下是我的代码。

AutomationElement 方式:

public static class IAutomationmanager
{

    /// <summary>
    /// Get the AutomationElement of Desktop (Root)
    /// </summary>
    /// <returns></returns>
    public static AutomationElement GetDesktop()
    {
        return AutomationElement.RootElement;
    }

        
    /// <summary>
    /// Get the AutomationElement of a window by process ID (PID)
    /// </summary>
    /// <param name="processID">Process ID used to get window element</param>
    /// <returns></returns>
    public static AutomationElement GetWindowByProcessID(int processID)
    {
        return GetDesktop().FindFirst(TreeScope.Children,new PropertyCondition(AutomationElement.ProcessIdProperty,processID));
    }


    /// <summary>
    /// Get the AutomationElementCollection of windows by process ID (PID)
    /// </summary>
    /// <param name="processID">Process ID used to get windows elements</param>
    /// <returns></returns>
    public static AutomationElementCollection GetWindowsByProcessID(int processID)
    {
        return GetDesktop().FindAll(TreeScope.Children,processID));
    }


    /// <summary>
    /// Get the elements that don't have children of an element
    /// </summary>
    /// <param name="element">Input element</param>
    /// <returns></returns>
    public static List<AutomationElement> GetEndElements(AutomationElement element)
    {
        List<AutomationElement> res = new List<AutomationElement>();
        try
        {
            Rect bounds = element.Current.BoundingRectangle;
            if (!JudgeBounding(bounds)) return res;
            GetEndElementsDFS(element,res,bounds);
        }
        catch (Exception)
        {
            return res;
        }
        return res;
    }


    /// <summary>
    /// Get the elements that don't have children of a collection of elements which don't have common descendents
    /// </summary>
    /// <param name="collection">A collection of elements which don't have common descendents</param>
    /// <returns></returns>
    public static List<AutomationElement> GetEndElements(AutomationElementCollection collection)
    {
        List<AutomationElement> res = new List<AutomationElement>();
        foreach (AutomationElement element in collection)
        {
            res.AddRange(GetEndElements(element));
        }
        return res;
    }


    public static ConcurrentQueue<AutomationElement> GetEndElementsByParallel(AutomationElement element,int concurrentThread)
    {
        ConcurrentQueue<AutomationElement> res = new ConcurrentQueue<AutomationElement>();
        try
        {
            Rect bounds = element.Current.BoundingRectangle;
            if (!JudgeBounding(bounds)) return res;
            IThreadPool<ThreadStart> threadPool = new IThreadPool<ThreadStart>(concurrentThread,new action<ThreadStart>((threadStart) =>
            {
                threadStart.Invoke();
            }));
            GetEndElementsDFS(element,threadPool,bounds);
            threadPool.Join();
        }
        catch (Exception)
        {
            return res;
        }
        return res;
    }


    private static bool JudgeBounding(Rect bounds)
    {
        try
        {
            if (bounds == null)
                return false;
            if (bounds.Width == 0 || bounds.Height == 0)
                return false;
            if (bounds.IsEmpty)
                return false;
            if (double.IsInfinity(bounds.Top) ||
                double.IsInfinity(bounds.Left) ||
                double.IsInfinity(bounds.Width) ||
                double.IsInfinity(bounds.Height))
                return false;
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }


    private static void GetEndElementsDFS(AutomationElement element,List<AutomationElement> res,Rect bounds)
    {
        AutomationElementCollection children = element.FindAll(TreeScope.Children,new PropertyCondition(AutomationElement.IsOffscreenProperty,false));
        foreach (AutomationElement e in children)
        {
            try
            {
                Rect rect = e.Current.BoundingRectangle;
                if (!JudgeBounding(rect)) continue;
                if (rect.Bottom <= bounds.Top || rect.Right <= bounds.Left) continue;
                if (rect.Top >= bounds.Bottom || rect.Left >= bounds.Right) continue;
                GetEndElementsDFS(e,rect);
            } catch (Exception)
            {
                continue;
            }
        }
        if (children.Count == 0) res.Add(element);
    }


    private static void GetEndElementsDFS(AutomationElement element,ConcurrentQueue<AutomationElement> res,IThreadPool<ThreadStart> threadPool,false));
        foreach (AutomationElement e in children)
        {
            try
            {
                Rect rect = e.Current.BoundingRectangle;
                if (!JudgeBounding(rect)) continue;
                if (rect.Bottom <= bounds.Top || rect.Right <= bounds.Left) continue;
                if (rect.Top >= bounds.Bottom || rect.Left >= bounds.Right) continue;
                if (threadPool.IdleWorkerCount > 0)
                {
                    threadPool.Enqueuetask(() =>
                    {
                        GetEndElementsDFS(e,rect);
                    });
                } else
                {
                    GetEndElementsDFS(e,rect);
                }
            }
            catch (Exception)
            {
                continue;
            }
        }
        if (children.Count == 0) res.Enqueue(element);
    }

}

IUIAutomationElement 方式:

class IUIAutomationmanager
{
    public static CUIAutomation automation = new CUIAutomation();
    private static IUIAutomationCondition ConditionNotOffScreen = 
        automation.CreatePropertyCondition(UIA_PropertyIds.UIA_IsOffscreenPropertyId,false);

    public static IUIAutomationElement GetDesktop()
    {
        return automation.GetRootElement();
    }


    public static IUIAutomationElement GetWindowByProcessID(int processID)
    {
        return GetDesktop().FindFirst(TreeScope.TreeScope_Children,automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ProcessIdPropertyId,processID));
    }


    public static List<IUIAutomationElement> GetEndElements(IUIAutomationElement element)
    {
        List<IUIAutomationElement> res = new List<IUIAutomationElement>();
        try
        {
            Rect bounds = GetBoundingRectangle(element);
            if (!JudgeBounding(bounds)) return res;
            GetEndElementsDFS(element,bounds);
        }
        catch (Exception)
        {
            return res;
        }
        return res;
    }

    public static ConcurrentQueue<IUIAutomationElement> GetEndElementsByParallel(IUIAutomationElement element,int concurrentThread)
    {
        ConcurrentQueue<IUIAutomationElement> res = new ConcurrentQueue<IUIAutomationElement>();
        try
        {
            Rect bounds = GetBoundingRectangle(element);
            if (!JudgeBounding(bounds)) return res;
            IThreadPool<ThreadStart> threadPool = new IThreadPool<ThreadStart>(concurrentThread,bounds);
            threadPool.Join();
        }
        catch (Exception)
        {
            return res;
        }
        return res;
    }


    public static Rect GetBoundingRectangle(IUIAutomationElement element)
    {
        double[] bounds = (double[])element.getcurrentPropertyValue(UIA_PropertyIds.UIA_BoundingRectanglePropertyId);
        if (bounds == null) { return new Rect(0,0); }
        return new Rect(bounds[0],bounds[1],bounds[2],bounds[3]);
    }


    private static bool JudgeBounding(Rect bounds)
    {
        try
        {
            if (bounds == null)
                return false;
            if (bounds.Width == 0 || bounds.Height == 0)
                return false;
            if (bounds.IsEmpty)
                return false;
            if (double.IsInfinity(bounds.Top) ||
                double.IsInfinity(bounds.Left) ||
                double.IsInfinity(bounds.Width) ||
                double.IsInfinity(bounds.Height))
                return false;
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }


    private static void GetEndElementsDFS(IUIAutomationElement element,List<IUIAutomationElement> res,Rect bounds)
    {
        IUIAutomationElementArray children = element.FindAll(TreeScope.TreeScope_Children,ConditionNotOffScreen);
        for (int i = 0; i < children.Length; i ++)
        {
            IUIAutomationElement e = children.GetElement(i);
            try
            {
                Rect rect = GetBoundingRectangle(e);
                if (!JudgeBounding(rect)) continue;
                if (rect.Bottom <= bounds.Top || rect.Right <= bounds.Left) continue;
                if (rect.Top >= bounds.Bottom || rect.Left >= bounds.Right) continue;
                GetEndElementsDFS(e,rect);
            }
            catch (Exception)
            {
                continue;
            }
        }
        if (children.Length == 0) res.Add(element);
    }

    private static void GetEndElementsDFS(IUIAutomationElement element,ConcurrentQueue<IUIAutomationElement> res,ConditionNotOffScreen);
        for (int i = 0; i < children.Length; i ++)
        {
            IUIAutomationElement e = children.GetElement(i);
            try
            {
                Rect rect = GetBoundingRectangle(e);
                if (!JudgeBounding(rect)) continue;
                if (rect.Bottom <= bounds.Top || rect.Right <= bounds.Left) continue;
                if (rect.Top >= bounds.Bottom || rect.Left >= bounds.Right) continue;
                if (threadPool.IdleWorkerCount > 0)
                {
                    threadPool.Enqueuetask(() =>
                    {
                        GetEndElementsDFS(e,rect);
                    });
                }
                else
                {
                    GetEndElementsDFS(e,rect);
                }
            }
            catch (Exception)
            {
                continue;
            }
        }
        if (children.Length == 0) res.Enqueue(element);
    }
}

ThreadPool

/// <summary>
/// Custom Thread Pool Implementation.
/// Modified from https://stackoverflow.com/questions/5826981/how-to-reuse-threads-in-net-3-5,/// by dashton@stackoverflow,ChrisWue@stackoverflow
/// </summary>
/// <typeparam name="T"></typeparam>
public class IThreadPool<T> : IDisposable where T : class
{
    private readonly object _locker = new object();
    private readonly object _idleLocker = new object();
    private readonly List<Thread> _workers;
    private readonly Queue<T> _taskQueue = new Queue<T>();
    private readonly action<T> _dequeueaction;
    public readonly long WorkerCount;

    private int _idleWorkerCount = 0;


    /// <summary>
    /// The count of idle worker
    /// </summary>
    public int IdleWorkerCount
    {
        get { return _idleWorkerCount; }
    }

    public bool Idle;

    /// <summary>
    /// Initializes a new instance of the <see cref="IThreadPool{T}"/> class.
    /// </summary>
    /// <param name="workerCount">The worker count.</param>
    /// <param name="dequeueaction">The dequeue action.</param>
    public IThreadPool(int workerCount,action<T> dequeueaction)
    {
        Idle = true;
        WorkerCount = workerCount;
        _idleWorkerCount = workerCount;
        _dequeueaction = dequeueaction;
        _workers = new List<Thread>(workerCount);

        // Create and start a separate thread for each worker
        for (int i = 0; i < workerCount; i ++)
        {
            Thread t = new Thread(Consume) { IsBackground = true,Name = string.Format("IThreadPool worker {0}",i) };
            _workers.Add(t);
            t.Start();
        }
    }


    /// <summary>
    /// Enqueues the task.
    /// </summary>
    /// <param name="task">The task.</param>
    public void Enqueuetask(T task)
    {
        lock (_locker)
        {
            _taskQueue.Enqueue(task);
            Idle = false;
            Monitor.PulseAll(_locker);
        }
    }


    /// <summary>
    /// Consumes this instance.
    /// </summary>
    private void Consume()
    {
        while (true)
        {
            T item;
            lock (_locker)
            {
                while (_taskQueue.Count == 0) Monitor.Wait(_locker);
                item = _taskQueue.Dequeue();
            }
            if (item == null) return;

            Interlocked.Decrement(ref _idleWorkerCount);
            // run actual method
            _dequeueaction(item);
            Interlocked.Increment(ref _idleWorkerCount);
            if (_idleWorkerCount == WorkerCount && _taskQueue.Count == 0)
            {
                lock (_idleLocker)
                {
                    Idle = true;
                    Monitor.PulseAll(_idleLocker);
                }
            } else Idle = false;
        }
    }


    /// <summary>
    /// Waiting for everything to finish.
    /// </summary>
    public void Join()
    {
        lock (_idleLocker)
        {
            while (!Idle) Monitor.Wait(_idleLocker);
        }
        Dispose();
    }


    /// <summary>
    /// Performs application-defined tasks associated with freeing,releasing,or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        // Enqueue one null task per worker to make each exit.
        _workers.ForEach(thread => Enqueuetask(null));
        _workers.ForEach(thread => thread.Join());
    }
}

我想知道是否有一种方法可以在 C# 中快速检索 UI 元素。

huilovenai 回答:如何在 C# 中以高性能获取 UI 树

暂时没有好的解决方案,如果你有好的解决方案,请发邮件至:iooj@foxmail.com
本文链接:https://www.f2er.com/2562.html

大家都在问