我现在通过 AutomationElement
和 IUIAutomationElement
在 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 元素。