1456 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Automation;
using System.Windows.Automation.Text;
using System.Windows;
using System.Collections.Generic;
using System.Web.Script.Serialization;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
public class AutomationManager
{
// UIA Control Type IDs
private static class UIA_ControlTypeIds
{
public const int Window = 50032;
}
// 用于缓存找到的元素
private static Dictionary<string, AutomationElement> elementCache = new Dictionary<string, AutomationElement>();
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[StructLayout(LayoutKind.Sequential)]
private struct Point
{
public int X;
public int Y;
public override bool Equals(object obj)
{
if (!(obj is Point)) return false;
Point other = (Point)obj;
return X == other.X && Y == other.Y;
}
public static bool operator ==(Point a, Point b)
{
return a.Equals(b);
}
public static bool operator !=(Point a, Point b)
{
return !a.Equals(b);
}
public override int GetHashCode()
{
return X.GetHashCode() ^ Y.GetHashCode();
}
public System.Windows.Point ToWindowsPoint()
{
return new System.Windows.Point(X, Y);
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
private const int GWL_EXSTYLE = -20;
private const int WS_EX_TRANSPARENT = 0x20;
private const int WS_EX_LAYERED = 0x80000;
private const int WS_EX_TOOLWINDOW = 0x80;
private const int WS_EX_NOACTIVATE = 0x08000000;
private const int HWND_TOPMOST = -1;
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[DllImport("user32.dll")]
private static extern bool SetCursorPos(int x, int y);
[DllImport("user32.dll")]
private static extern void mouse_event(uint dwFlags, int dx, int dy, uint dwData, int dwExtraInfo);
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
// 添加静态字段
private static System.Windows.Forms.Timer mouseTimer;
private static Form overlayForm;
private static Form previewForm;
private static AutomationElement lastElement;
private static bool completed;
private static CacheRequest CreateCacheRequest()
{
var cacheRequest = new CacheRequest();
cacheRequest.Add(AutomationElement.NameProperty);
cacheRequest.Add(AutomationElement.ClassNameProperty);
cacheRequest.Add(AutomationElement.ControlTypeProperty);
cacheRequest.Add(AutomationElement.BoundingRectangleProperty);
cacheRequest.Add(AutomationElement.IsOffscreenProperty);
cacheRequest.Add(AutomationElement.IsEnabledProperty);
cacheRequest.TreeScope = TreeScope.Element | TreeScope.Children | TreeScope.Descendants;
return cacheRequest;
}
private static AutomationElement GetTaskbarElement()
{
IntPtr taskbarHandle = FindWindow("Shell_TrayWnd", null);
if (taskbarHandle != IntPtr.Zero)
{
return AutomationElement.FromHandle(taskbarHandle);
}
return null;
}
private static List<AutomationElement> GetTaskbarChildren(AutomationElement taskbarElement)
{
var children = new List<AutomationElement>();
if (taskbarElement == null) return children;
try
{
var cacheRequest = CreateCacheRequest();
cacheRequest.Push();
try
{
var conditions = new AndCondition(
new PropertyCondition(AutomationElement.IsOffscreenProperty, false),
new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.IsContentElementProperty, true)
);
var taskbarChildren = taskbarElement.FindAll(TreeScope.Children | TreeScope.Descendants, conditions);
foreach (AutomationElement child in taskbarChildren)
{
children.Add(child);
}
}
finally
{
cacheRequest.Pop();
}
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error: {0}", ex.Message));
}
return children;
}
private static void InspectElementByPosition(string position)
{
Point cursorPos;
if (string.IsNullOrEmpty(position))
{
// 获取当前鼠标位置
GetCursorPos(out cursorPos);
}
else
{
// 解析传入的坐标
string[] coords = position.Split(',');
cursorPos = new Point
{
X = int.Parse(coords[0]),
Y = int.Parse(coords[1])
};
}
var element = AutomationElement.FromPoint(cursorPos.ToWindowsPoint());
if (element != null)
{
InspectElementInfo(element, cursorPos);
return;
}
throw new Exception("在指定坐标未找到元素");
}
public static void Main(string[] args)
{
if (args.Length == 0 || args[0] == "-h" || args[0] == "--help")
{
ShowHelp();
return;
}
string type = GetArgumentValue(args, "-type");
if (string.IsNullOrEmpty(type))
{
Console.Error.WriteLine("Error: 必须指定操作类型 (-type)");
return;
}
try
{
switch (type.ToLower())
{
case "inspect":
string position = GetArgumentValue(args, "-position");
if (position != null) // 参数存在(一定会有值,可能是空字符串)
{
InspectElementByPosition(position);
}
else // 参数不存在
{
InspectElement(args);
}
break;
case "click":
ClickElement(GetTargetElement(args));
break;
case "setvalue":
string value = GetArgumentValue(args, "-value");
SetElementValue(GetTargetElement(args), value);
string sendenter = GetArgumentValue(args, "-sendenter");
if (sendenter != null)
{
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
}
break;
case "getvalue":
GetElementValue(GetTargetElement(args));
break;
case "select":
string item = GetArgumentValue(args, "-item");
SelectItem(GetTargetElement(args), item);
break;
case "expand":
string expandStr = GetArgumentValue(args, "-expand");
bool expand = false;
if (!string.IsNullOrEmpty(expandStr))
{
expand = expandStr.ToLower() == "true";
}
ExpandElement(GetTargetElement(args), expand);
break;
case "scroll":
string direction = GetArgumentValue(args, "-direction");
if (string.IsNullOrEmpty(direction))
{
direction = "vertical";
}
string amountStr = GetArgumentValue(args, "-amount");
double amount = 0;
if (!string.IsNullOrEmpty(amountStr))
{
amount = double.Parse(amountStr);
}
ScrollElement(GetTargetElement(args), direction, amount);
break;
case "wait":
string timeoutStr = GetArgumentValue(args, "-timeout");
int timeout = 30;
if (!string.IsNullOrEmpty(timeoutStr))
{
timeout = int.Parse(timeoutStr);
}
WaitForElement(args, timeout);
break;
case "focus":
SetFocus(GetTargetElement(args));
break;
case "highlight":
string durationStr = GetArgumentValue(args, "-duration");
int duration = 2;
if (!string.IsNullOrEmpty(durationStr))
{
duration = int.Parse(durationStr);
}
HighlightElement(GetTargetElement(args), duration);
break;
case "sendkeys":
string keys = GetArgumentValue(args, "-keys");
SendKeys(GetTargetElement(args), keys);
break;
default:
throw new Exception(string.Format("不支持的操作类型: {0}", type));
}
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error: {0}", ex.Message));
Environment.Exit(1);
}
}
private static AutomationElement GetTargetElement(string[] args)
{
// 获取起始窗口
AutomationElement root;
string windowArg = GetArgumentValue(args, "-window");
if (!string.IsNullOrEmpty(windowArg))
{
// 通过窗口句柄查找
int handle = int.Parse(windowArg);
root = AutomationElement.FromHandle(new IntPtr(handle));
}
else
{
// 使用当前活动窗口
IntPtr activeHandle = GetForegroundWindow();
root = AutomationElement.FromHandle(activeHandle);
}
if (root == null)
{
throw new Exception("无法获取指定的窗口");
}
// 通过 XPath 查找(优先)
string xpath = GetArgumentValue(args, "-xpath");
if (!string.IsNullOrEmpty(xpath))
{
var element = FindElementByXPath(xpath, root);
if (element != null) return element;
throw new Exception(string.Format("找不到指定的XPath: {0}", xpath));
}
// 通过 AutomationId 查找
string id = GetArgumentValue(args, "-id");
if (!string.IsNullOrEmpty(id))
{
var element = root.FindFirst(TreeScope.Subtree,
new PropertyCondition(AutomationElement.AutomationIdProperty, id));
if (element != null) return element;
throw new Exception(string.Format("找不到指定的AutomationId: {0}", id));
}
// 通过 Name 查找
string name = GetArgumentValue(args, "-name");
if (!string.IsNullOrEmpty(name))
{
var element = root.FindFirst(TreeScope.Subtree,
new PropertyCondition(AutomationElement.NameProperty, name));
if (element != null) return element;
throw new Exception(string.Format("找不到指定的Name: {0}", name));
}
// 通过组合条件查找
string condition = GetArgumentValue(args, "-condition");
if (!string.IsNullOrEmpty(condition))
{
var conditions = new List<Condition>();
string[] parts = condition.Split(';');
foreach (string part in parts)
{
string[] keyValue = part.Split('=');
if (keyValue.Length == 2)
{
switch (keyValue[0].ToLower())
{
case "name":
conditions.Add(new PropertyCondition(AutomationElement.NameProperty, keyValue[1]));
break;
case "class":
conditions.Add(new PropertyCondition(AutomationElement.ClassNameProperty, keyValue[1]));
break;
case "automation":
conditions.Add(new PropertyCondition(AutomationElement.AutomationIdProperty, keyValue[1]));
break;
case "type":
string controlTypeName = keyValue[1];
if (controlTypeName.StartsWith("ControlType."))
{
controlTypeName = controlTypeName.Substring("ControlType.".Length);
}
var field = typeof(ControlType).GetField(controlTypeName);
if (field != null)
{
ControlType controlType = (ControlType)field.GetValue(null);
conditions.Add(new PropertyCondition(AutomationElement.ControlTypeProperty, controlType));
}
break;
}
}
}
if (conditions.Count > 0)
{
Condition searchCondition;
if (conditions.Count > 1)
{
searchCondition = new AndCondition(conditions.ToArray());
}
else
{
searchCondition = conditions[0];
}
var element = root.FindFirst(TreeScope.Subtree, searchCondition);
if (element != null) return element;
throw new Exception(string.Format("找不到符合条件的元素: {0}", condition));
}
}
throw new Exception("必须指定元素的识别方式: -xpath, -id, -name 或 -condition");
}
private static void ShowHelp()
{
string help = @"UI自动化工具
用法: automation.exe -type <操作类型> [参数]
通用参数:
-window <窗口句柄> 指定要操作的窗口,如果不指定则使用当前活动窗口
操作类型:
1. inspect - 检查元素
无需其他参数,点击要检查的元素即可
2. click - 点击元素
-xpath <XPath路径> 或
-id <AutomationId> 或
-name <名称> 或
-condition ""name=xx;type=Button""
3. setvalue - 设置值
-xpath <XPath路径> -value <新值>
4. getvalue - 获取值
-xpath <XPath路径>
5. select - 选择项目
-xpath <XPath路径> -item <项目名称>
6. expand - 展开/折叠
-xpath <XPath路径> -expand <true/false>
7. scroll - 滚动
-xpath <XPath路径> -direction <vertical/horizontal> -amount <0-100>
8. wait - 等待元素
-xpath <XPath路径> -timeout <秒数>
9. focus - 设置焦点
-xpath <XPath路径>
10. highlight - 高亮显示
-xpath <XPath路径> -duration <秒数>
11. sendkeys - 发送按键
-xpath <XPath路径> -keys <按键>
按键格式说明:
- 普通字符直接输入,如 ""abc""
- 特殊按键用 {} 包围,如 {ENTER}、{TAB}
- 组合键,如 ^c 表示 Ctrl+C
- 支持的特殊按键:
{BACKSPACE}, {BS}, {BKSP} - 退格键
{BREAK} - Break键
{CAPSLOCK} - Caps Lock键
{DELETE}, {DEL} - Delete键
{DOWN} - 向下键
{END} - End键
{ENTER}, {RETURN} - Enter键
{ESC} - Esc键
{HELP} - Help键
{HOME} - Home键
{INSERT}, {INS} - Insert键
{LEFT} - 向左键
{NUMLOCK} - Num Lock键
{PGDN} - Page Down键
{PGUP} - Page Up键
{PRTSC} - Print Screen键
{RIGHT} - 向右键
{SCROLLLOCK} - Scroll Lock键
{TAB} - Tab键
{UP} - 向上键
{F1} - {F12} - 功能键
{ADD} - 数字键盘加号键
{SUBTRACT} - 数字键盘减号键
{MULTIPLY} - 数字键盘乘号键
{DIVIDE} - 数字键盘除号键
{NUMPAD0} - {NUMPAD9} - 数字键盘数字键
修饰键:
+ (加号) - SHIFT
^ (脱字号) - CTRL
% (百分号) - ALT
示例:
""Hello"" - 输入 Hello
""{ENTER}"" - 按 Enter 键
""^c"" - 按 Ctrl+C
""^{HOME}"" - 按 Ctrl+Home
""%{F4}"" - 按 Alt+F4
""+{TAB}"" - 按 Shift+Tab";
Console.WriteLine(help);
}
private static string GetArgumentValue(string[] args, string key, bool checkNextArg = true)
{
int index = Array.IndexOf(args, key);
if (index >= 0)
{
if (index < args.Length - 1)
{
// 如果需要检查下一个参数是否是参数名
if (checkNextArg && args[index + 1].StartsWith("-"))
{
return ""; // 参数存在但没有值,返回空字符串
}
return args[index + 1];
}
return ""; // 参数存在但没有值,返回空字符串
}
return null; // 参数不存在
}
private static void SetElementValue(AutomationElement element, string value)
{
if (string.IsNullOrEmpty(value))
{
throw new Exception("必须指定要设置的值");
}
try
{
var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (valuePattern != null)
{
valuePattern.SetValue(value);
Console.WriteLine("true");
return;
}
throw new Exception("元素不支持设置值操作");
}
catch (Exception ex)
{
throw new Exception("设置值失败: " + ex.Message);
}
}
private static void GetElementValue(AutomationElement element)
{
try
{
var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (valuePattern != null)
{
Console.WriteLine(valuePattern.Current.Value);
return;
}
throw new Exception("元素不支持获取值操作");
}
catch (Exception ex)
{
throw new Exception("获取值失败: " + ex.Message);
}
}
private static void SelectItem(AutomationElement element, string item)
{
if (string.IsNullOrEmpty(item))
{
throw new Exception("必须指定要选择的项目");
}
try
{
var selectionPattern = element.GetCurrentPattern(SelectionPattern.Pattern) as SelectionPattern;
if (selectionPattern != null)
{
var children = element.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach (AutomationElement child in children)
{
if (child.Current.Name == item)
{
var selectionItemPattern = child.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
if (selectionItemPattern != null)
{
selectionItemPattern.Select();
Console.WriteLine("true");
return;
}
}
}
throw new Exception("找不到指定的项目: " + item);
}
throw new Exception("元素不支持选择操作");
}
catch (Exception ex)
{
throw new Exception("选择操作失败: " + ex.Message);
}
}
private static void ExpandElement(AutomationElement element, bool expand)
{
try
{
var expandCollapsePattern = element.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern;
if (expandCollapsePattern != null)
{
if (expand)
expandCollapsePattern.Expand();
else
expandCollapsePattern.Collapse();
Console.WriteLine("true");
return;
}
throw new Exception("元素不支持展开/折叠操作");
}
catch (Exception ex)
{
throw new Exception("展开/折叠操作失败: " + ex.Message);
}
}
private static void ScrollElement(AutomationElement element, string direction, double amount)
{
try
{
var scrollPattern = element.GetCurrentPattern(ScrollPattern.Pattern) as ScrollPattern;
if (scrollPattern != null)
{
if (direction.ToLower() == "horizontal")
scrollPattern.SetScrollPercent(amount, ScrollPattern.NoScroll);
else
scrollPattern.SetScrollPercent(ScrollPattern.NoScroll, amount);
Console.WriteLine("true");
return;
}
throw new Exception("元素不支持滚动操作");
}
catch (Exception ex)
{
throw new Exception("滚动操作失败: " + ex.Message);
}
}
private static void WaitForElement(string[] args, int timeout)
{
DateTime endTime = DateTime.Now.AddSeconds(timeout);
while (DateTime.Now < endTime)
{
try
{
GetTargetElement(args);
Console.WriteLine("true");
return;
}
catch
{
Thread.Sleep(500);
}
}
throw new Exception("等待超时");
}
private static AutomationElement FindElementByXPath(string xpath, AutomationElement root)
{
string[] segments = xpath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
AutomationElement current = root;
foreach (string segment in segments)
{
if (string.IsNullOrEmpty(segment)) continue;
// 解析控件类型和索引
string controlType = segment;
int index = 1;
string condition = "";
// 提取索引 [n]
int indexStart = segment.IndexOf('[');
if (indexStart > 0)
{
int indexEnd = segment.IndexOf(']', indexStart);
if (indexEnd > indexStart)
{
controlType = segment.Substring(0, indexStart);
string indexStr = segment.Substring(indexStart + 1, indexEnd - indexStart - 1);
// 检查是否是属性条件
if (indexStr.StartsWith("@"))
{
condition = indexStr;
}
else
{
int parsedIndex;
if (int.TryParse(indexStr, out parsedIndex))
{
index = parsedIndex;
}
}
}
}
// 创建控件类型条件
List<Condition> conditions = new List<Condition>();
if (controlType.StartsWith("ControlType."))
{
controlType = controlType.Substring("ControlType.".Length);
}
var field = typeof(ControlType).GetField(controlType);
if (field != null)
{
ControlType type = (ControlType)field.GetValue(null);
conditions.Add(new PropertyCondition(AutomationElement.ControlTypeProperty, type));
}
// 添加属性条件
if (!string.IsNullOrEmpty(condition))
{
if (condition.StartsWith("@Name='"))
{
string name = condition.Substring("@Name='".Length).TrimEnd('\'');
conditions.Add(new PropertyCondition(AutomationElement.NameProperty, name));
}
else if (condition.StartsWith("@AutomationId='"))
{
string automationId = condition.Substring("@AutomationId='".Length).TrimEnd('\'');
conditions.Add(new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));
}
else if (condition.StartsWith("@ClassName='"))
{
string className = condition.Substring("@ClassName='".Length).TrimEnd('\'');
conditions.Add(new PropertyCondition(AutomationElement.ClassNameProperty, className));
}
}
// 查找元素
Condition finalCondition = conditions.Count > 1
? new AndCondition(conditions.ToArray())
: conditions[0];
var elements = current.FindAll(TreeScope.Children, finalCondition);
if (elements.Count == 0) return null;
// 使用索引选择元素
if (index > elements.Count) return null;
current = elements[index - 1]; // 转换为0基索引
}
return current;
}
private static void SetFocus(AutomationElement element)
{
try
{
element.SetFocus();
Console.WriteLine("true");
}
catch (Exception ex)
{
throw new Exception("设置焦点失败: " + ex.Message);
}
}
private static void HighlightElement(AutomationElement element, int duration)
{
var rect = element.Current.BoundingRectangle;
var highlightForm = new Form
{
StartPosition = FormStartPosition.Manual,
Location = new System.Drawing.Point((int)rect.Left, (int)rect.Top),
Size = new System.Drawing.Size((int)rect.Width, (int)rect.Height),
BackColor = Color.Yellow,
Opacity = 0.3,
ShowInTaskbar = false,
FormBorderStyle = FormBorderStyle.None,
TopMost = true
};
try
{
highlightForm.Show();
Console.WriteLine("正在高亮显示元素");
Thread.Sleep(duration * 1000);
}
finally
{
highlightForm.Close();
highlightForm.Dispose();
}
}
private struct ElementHierarchyInfo
{
public string XPath;
public IntPtr WindowHandle;
}
// 获取元素的层次结构信息
private static ElementHierarchyInfo GetElementHierarchyInfo(AutomationElement element)
{
var path = new List<string>();
var current = element;
var walker = TreeWalker.ControlViewWalker;
IntPtr windowHandle = IntPtr.Zero;
// 循环直到找到根元素
while (current != null && current != AutomationElement.RootElement)
{
var parent = walker.GetParent(current);
// 是否是最后一个元素
bool isLastElement = (parent == AutomationElement.RootElement || parent == null);
// 是否是句柄不为0的窗口
bool isValidWindow = (current.Current.ControlType.Id == UIA_ControlTypeIds.Window && current.Current.NativeWindowHandle != 0);
// 是否是任务栏
bool isTaskbar = (current.Current.ClassName == "Shell_TrayWnd" ||
current.Current.ClassName == "Shell_SecondaryTrayWnd");
// 如果是窗口/任务栏,或者是最后一个元素,获取其句柄
if (isValidWindow || isTaskbar || isLastElement)
{
windowHandle = new IntPtr(current.Current.NativeWindowHandle);
break; // 获取到句柄后就停止遍历
}
else
{
// 获取同级元素中的索引
int index = 1;
var siblings = parent.FindAll(TreeScope.Children, new PropertyCondition(
AutomationElement.ControlTypeProperty, current.Current.ControlType));
foreach (AutomationElement sibling in siblings)
{
if (sibling == current) break;
index++;
}
// 构建路径段
string type = current.Current.ControlType.ProgrammaticName.Replace("ControlType.", "");
string pathSegment = type;
// 如果有多个同类型元素,添加索引
if (siblings.Count > 1)
{
pathSegment = string.Format("{0}[{1}]", type, index);
}
path.Insert(0, pathSegment);
}
current = parent;
}
return new ElementHierarchyInfo
{
XPath = "/" + string.Join("/", path),
WindowHandle = windowHandle
};
}
private static void ClickElement(AutomationElement element)
{
if (element == null)
{
throw new Exception("未找到目标元素");
}
try
{
// 首先尝试使用 Invoke 模式(适用于按钮等)
object invokePattern;
if (element.TryGetCurrentPattern(InvokePattern.Pattern, out invokePattern))
{
((InvokePattern)invokePattern).Invoke();
Console.WriteLine("true");
return;
}
// 尝试使用 SelectionItem 模式(适用于列表项、单选框等)
object selectionItemPattern;
if (element.TryGetCurrentPattern(SelectionItemPattern.Pattern, out selectionItemPattern))
{
((SelectionItemPattern)selectionItemPattern).Select();
Console.WriteLine("true");
return;
}
// 尝试使用 Toggle 模式(适用于复选框等)
object togglePattern;
if (element.TryGetCurrentPattern(TogglePattern.Pattern, out togglePattern))
{
((TogglePattern)togglePattern).Toggle();
Console.WriteLine("true");
return;
}
// 如果都不支持,尝试使用鼠标点击
try
{
// 激活元素
element.SetFocus();
// 获取元素的中心点坐标
System.Windows.Point clickablePoint = element.GetClickablePoint();
// 转换为屏幕坐标
var rect = element.Current.BoundingRectangle;
if (rect.IsEmpty)
{
throw new Exception("无法获取元素位置");
}
// 保存当前鼠标位置
Point currentMousePosition;
GetCursorPos(out currentMousePosition);
// 使用 mouse_event 执行点击
int x = (int)clickablePoint.X;
int y = (int)clickablePoint.Y;
// 移动鼠标到目标位置
SetCursorPos(x, y);
Thread.Sleep(50); // 短暂延迟确保鼠标移动到位
// 模拟鼠标点击
mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0);
Thread.Sleep(50); // 短暂延迟模拟真实点击
mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, 0);
// 恢复鼠标位置
SetCursorPos(currentMousePosition.X, currentMousePosition.Y);
Console.WriteLine("true");
}
catch (Exception ex)
{
throw new Exception(string.Format("鼠标点击失败: {0}", ex.Message));
}
}
catch (Exception ex)
{
throw new Exception(string.Format("点击操作失败: {0}", ex.Message));
}
}
private static void SendKeys(AutomationElement element, string keys)
{
if (string.IsNullOrEmpty(keys))
{
throw new Exception("必须指定要发送的按键");
}
try
{
// 确保元素可以接收输入
if (!element.Current.IsKeyboardFocusable)
{
throw new Exception("元素不支持键盘输入");
}
element.SetFocus();
System.Windows.Forms.SendKeys.SendWait(keys);
Console.WriteLine("true");
}
catch (Exception ex)
{
throw new Exception("发送按键失败: " + ex.Message);
}
}
// 将 HandleKeyPress 方法移到类级别
private static void HandleKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)27) // ESC键
{
mouseTimer.Stop();
overlayForm.Close();
previewForm.Close();
Environment.Exit(0);
}
else if (e.KeyChar == 'c' || e.KeyChar == 'C') // 添加复制功能
{
if (lastElement != null)
{
try
{
Clipboard.SetText(lastElement.Current.Name);
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error copying name: {0}", ex.Message));
}
}
}
}
// 元素检查器
private static void InspectElement(string[] args)
{
// 创建一个半透明遮罩窗口
overlayForm = new Form();
overlayForm.FormBorderStyle = FormBorderStyle.None;
overlayForm.StartPosition = FormStartPosition.Manual;
overlayForm.TopMost = true;
overlayForm.BackColor = Color.Blue;
overlayForm.Opacity = 0.15;
overlayForm.ShowInTaskbar = false;
overlayForm.KeyPreview = true;
// 设置窗口样式,确保能显示在任务栏上方
overlayForm.Load += (sender, e) =>
{
var hwnd = overlayForm.Handle;
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED);
};
// 创建预览窗口
previewForm = new Form
{
FormBorderStyle = FormBorderStyle.None,
StartPosition = FormStartPosition.Manual,
BackColor = Color.FromArgb(40, 40, 40),
ShowInTaskbar = false,
TopMost = true,
Opacity = 0.9,
AutoSize = true, // 添加自动尺寸
AutoSizeMode = AutoSizeMode.GrowAndShrink, // 根据内容调整大小
KeyPreview = true
};
Label previewLabel = new Label
{
AutoSize = true,
Dock = DockStyle.None,
TextAlign = ContentAlignment.MiddleLeft,
Font = new Font("楷体", 9),
ForeColor = Color.White,
Padding = new Padding(5),
AutoEllipsis = true,
UseMnemonic = false,
MaximumSize = new System.Drawing.Size(600, 0)
};
previewForm.Controls.Add(previewLabel);
// 设置预览窗口样式
previewForm.Load += (sender, e) =>
{
var hwnd = previewForm.Handle;
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED);
// 确保窗口显示在最顶层
SetWindowPos(
hwnd,
new IntPtr(HWND_TOPMOST),
previewForm.Left,
previewForm.Top,
previewForm.Width,
previewForm.Height,
0x0040 // SWP_SHOWWINDOW
);
};
// 获取根元素
var rootElement = AutomationElement.RootElement;
completed = false; // 使用静态字段
Rectangle lastRect = Rectangle.Empty;
lastElement = null;
Point lastCursorPos = new Point();
AutomationElement taskbarElement = null;
List<AutomationElement> taskbarChildren = null;
// 设置/取消窗口鼠标穿透的辅助方法
Action<bool> setMousePenetrate = (penetrate) =>
{
var hwnd = overlayForm.Handle;
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (penetrate)
{
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
}
else
{
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle & ~WS_EX_TRANSPARENT);
}
};
// 更新遮罩层位置的辅助方法
Action<Rectangle> updateOverlayPosition = (rect) =>
{
overlayForm.Location = new System.Drawing.Point(rect.Left, rect.Top);
overlayForm.Size = new System.Drawing.Size(rect.Width, rect.Height);
// 确保遮罩层始终在最顶层
SetWindowPos(
overlayForm.Handle,
new IntPtr(HWND_TOPMOST),
rect.Left,
rect.Top,
rect.Width,
rect.Height,
0x0040 // SWP_SHOWWINDOW
);
overlayForm.Refresh();
};
// 添加一个新的辅助方法来更新预览窗口位置
Action<int, int> updatePreviewPosition = (x, y) =>
{
// 确保预览窗口始终在最顶层
SetWindowPos(
previewForm.Handle,
new IntPtr(HWND_TOPMOST),
x,
y,
previewForm.Width,
previewForm.Height,
0x0040 // SWP_SHOWWINDOW
);
previewForm.Refresh();
};
// 创建一个定时器来处理鼠标位置检测
mouseTimer = new System.Windows.Forms.Timer();
mouseTimer.Interval = 100;
mouseTimer.Tick += (sender, e) =>
{
try
{
Point cursorPos;
GetCursorPos(out cursorPos);
if (cursorPos == lastCursorPos)
{
return;
}
lastCursorPos = cursorPos;
setMousePenetrate(true);
// 获取鼠标位置的元素
var element = AutomationElement.FromPoint(cursorPos.ToWindowsPoint());
setMousePenetrate(false);
if (element != null && element != rootElement)
{
// AutomationElement.FromPoint只能获得到任务栏
// 无法获得到任务栏的子元素,所以需要遍历
element = HandleTaskbarElement(
element,
cursorPos,
ref taskbarElement,
ref taskbarChildren
);
var elementBounds = element.Current.BoundingRectangle;
Rectangle elementRect = new Rectangle(
(int)elementBounds.Left,
(int)elementBounds.Top,
(int)elementBounds.Width,
(int)elementBounds.Height
);
bool elementChanged = lastElement == null ||
!element.Equals(lastElement) ||
elementRect != lastRect;
if (elementChanged)
{
lastElement = element;
lastRect = elementRect;
// 获取元素后,显示遮罩层
if (!overlayForm.Visible)
{
overlayForm.Show();
}
updateOverlayPosition(elementRect);
// 计算预览窗口位置
int previewX = elementRect.Right + 10;
int previewY = elementRect.Top;
// 确保预览窗口不会超出屏幕
Screen screen = Screen.FromPoint(new System.Drawing.Point(previewX, previewY));
if (previewX + previewForm.Width > screen.Bounds.Right)
{
previewX = elementRect.Left - previewForm.Width - 10;
}
if (previewY + previewForm.Height > screen.Bounds.Bottom)
{
previewY = screen.Bounds.Bottom - previewForm.Height;
}
updatePreviewPosition(previewX, previewY);
}
// 处理名称长度
string elementName = element.Current.Name;
if (elementName.Length > 50)
{
elementName = elementName.Substring(0, 47) + "...";
}
// 更新预览窗口内容
previewLabel.Text = string.Format(
"坐标: {0}, {1}\r\n" +
"名称: {2}\r\n" +
"大小: {3}x{4}\r\n" +
"类型: {5}\r\n" +
"C复制名称ESC退出",
cursorPos.X,
cursorPos.Y,
elementName,
elementRect.Width,
elementRect.Height,
element.Current.ControlType.ProgrammaticName.Replace("ControlType.", ""));
previewForm.Show();
}
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error: {0}", ex.Message));
}
};
overlayForm.MouseClick += (sender, e) =>
{
if (e.Button == MouseButtons.Left && lastElement != null)
{
try
{
mouseTimer.Stop();
overlayForm.Hide();
previewForm.Hide();
InspectElementInfo(lastElement, lastCursorPos);
completed = true;
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error: {0}", ex.Message));
}
finally
{
overlayForm.Close();
previewForm.Close();
}
}
};
// 添加按键事件处理
overlayForm.KeyPress += (sender, e) =>
{
HandleKeyPress(sender, e);
};
previewForm.KeyPress += (sender, e) =>
{
HandleKeyPress(sender, e);
};
// 在新线程中显示窗口
Thread thread = new Thread(() =>
{
mouseTimer.Start();
while (!completed)
{
Application.DoEvents();
Thread.Sleep(100);
}
mouseTimer.Stop();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
private static Dictionary<string, object> GetWindowInfoFromHandle(IntPtr hwnd)
{
var windowInfo = new Dictionary<string, object>();
if (hwnd != IntPtr.Zero)
{
StringBuilder title = new StringBuilder(256);
StringBuilder className = new StringBuilder(256);
GetWindowText(hwnd, title, title.Capacity);
GetClassName(hwnd, className, className.Capacity);
// 获取窗口位置和大小
RECT rect = new RECT();
GetWindowRect(hwnd, ref rect);
windowInfo.Add("title", title.ToString());
windowInfo.Add("class", className.ToString());
windowInfo.Add("handle", hwnd.ToInt32());
windowInfo.Add("x", rect.Left);
windowInfo.Add("y", rect.Top);
windowInfo.Add("width", rect.Right - rect.Left);
windowInfo.Add("height", rect.Bottom - rect.Top);
// 获取进程信息
uint processId;
GetWindowThreadProcessId(hwnd, out processId);
try
{
var process = System.Diagnostics.Process.GetProcessById((int)processId);
windowInfo.Add("processName", process.ProcessName);
windowInfo.Add("processPath", process.MainModule.FileName);
}
catch { }
}
return windowInfo;
}
// 打印元素的完整信息
private static void InspectElementInfo(AutomationElement element, Point position)
{
var hierarchyInfo = GetElementHierarchyInfo(element);
Dictionary<string, object> result = new Dictionary<string, object>();
// 元素信息
Dictionary<string, object> elementInfo = new Dictionary<string, object>();
elementInfo.Add("name", element.Current.Name);
elementInfo.Add("class", element.Current.ClassName);
elementInfo.Add("type", element.Current.ControlType.ProgrammaticName.Replace("ControlType.", ""));
elementInfo.Add("automationId", element.Current.AutomationId);
elementInfo.Add("xpath", hierarchyInfo.XPath);
elementInfo.Add("handle", element.Current.NativeWindowHandle);
// 添加元素位置和大小信息
var bounds = element.Current.BoundingRectangle;
elementInfo.Add("x", (int)bounds.Left);
elementInfo.Add("y", (int)bounds.Top);
elementInfo.Add("width", (int)bounds.Width);
elementInfo.Add("height", (int)bounds.Height);
// 添加控件文本值
try
{
var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
if (valuePattern != null)
{
elementInfo.Add("value", valuePattern.Current.Value);
}
}
catch { }
// 根据传入的坐标获取窗口信息
if (hierarchyInfo.WindowHandle != IntPtr.Zero)
{
var windowInfo = GetWindowInfoFromHandle(hierarchyInfo.WindowHandle);
foreach (var kvp in windowInfo)
{
result.Add(kvp.Key, kvp.Value);
}
}
// 将元素信息添加到结果中
result.Add("element", elementInfo);
// 添加坐标信息
result.Add("position", new Dictionary<string, int> {
{ "x", position.X },
{ "y", position.Y }
});
var serializer = new JavaScriptSerializer();
Console.Write(serializer.Serialize(result));
}
private static AutomationElement HandleTaskbarElement(AutomationElement element, Point cursorPos, ref AutomationElement taskbarElement, ref List<AutomationElement> taskbarChildren)
{
// 检查是否是任务栏元素
bool isTaskbarElement = element.Current.ClassName == "Shell_TrayWnd" ||
element.Current.ClassName == "Shell_SecondaryTrayWnd";
// 如果是新的任务栏元素,获取其所有子元素
if (isTaskbarElement && element != taskbarElement)
{
taskbarElement = GetTaskbarElement();
if (taskbarElement != null)
{
taskbarChildren = GetTaskbarChildren(taskbarElement);
}
}
// 如果是任务栏区域,在缓存的子元素中查找
if (isTaskbarElement && taskbarChildren != null && taskbarChildren.Count > 0)
{
var point = cursorPos.ToWindowsPoint();
AutomationElement bestMatch = null;
double minArea = double.MaxValue;
foreach (var child in taskbarChildren)
{
try
{
var childRect = child.Cached.BoundingRectangle;
if (childRect.Contains(point))
{
double area = childRect.Width * childRect.Height;
if (area < minArea && area > 0)
{
minArea = area;
bestMatch = child;
}
}
}
catch { }
}
if (bestMatch != null)
{
return bestMatch;
}
}
return element;
}
}