发送鼠标点击事件
概述
FBro提供了两种鼠标事件模拟方式:VIP鼠标事件和发送鼠标点击事件。其中发送鼠标点击事件不需要VIP控制接口,可以通过选择器自动获取元素坐标并执行点击操作,特别适用于精确的元素点击场景。
核心概念
两种鼠标事件方式对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| VIP鼠标事件 | 灵活性高,支持复杂操作 | 需要手动获取坐标 | 拖拽、复杂交互 |
| 发送鼠标点击事件 | 自动定位元素,精确度高 | 只支持基础点击 | 按钮点击、链接点击 |
关键组件
- GetMainTianBiaoFrame():获取浏览器主填表框架
- GetPoint():通过选择器获取元素坐标
- SendMouseClickEvent():发送鼠标点击事件
- FBroSharpMouseEvent:鼠标事件数据结构
基础实现
1. 通过选择器自动点击元素
csharp
/// <summary>
/// 通过CSS选择器点击元素
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="selector">CSS选择器</param>
/// <param name="index">元素索引(从0开始)</param>
/// <returns>是否点击成功</returns>
public bool ClickElementBySelector(IFBroSharpBrowser browser, string selector, int index = 0)
{
try
{
var callback = new FBroSharpJsCallbackSyn();
// 获取元素坐标
browser.GetMainTianBiaoFrame().GetPoint(selector, index, callback);
if (callback.help != null && callback.help.IsValid)
{
// 等待获取坐标结果
callback.help.WaitEvent(10 * 1000);
if (callback.help.HavStringData())
{
string pointData = callback.help.GetStringData();
Console.WriteLine($"获取到坐标: {pointData}");
// 解析坐标
var coordinates = ParseCoordinates(pointData);
if (coordinates.HasValue)
{
// 执行点击
return PerformMouseClick(browser, coordinates.Value.x, coordinates.Value.y);
}
}
}
Console.WriteLine($"无法获取元素坐标: {selector}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"点击元素失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 解析坐标字符串
/// </summary>
/// <param name="pointData">坐标数据(格式:x,y)</param>
/// <returns>坐标值</returns>
private (int x, int y)? ParseCoordinates(string pointData)
{
try
{
string[] parts = pointData.Split(',');
if (parts.Length >= 2)
{
float x = float.Parse(parts[0]);
float y = float.Parse(parts[1]);
return ((int)x, (int)y);
}
}
catch (Exception ex)
{
Console.WriteLine($"解析坐标失败: {ex.Message}");
}
return null;
}
/// <summary>
/// 执行鼠标点击
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="x">X坐标</param>
/// <param name="y">Y坐标</param>
/// <returns>是否成功</returns>
private bool PerformMouseClick(IFBroSharpBrowser browser, int x, int y)
{
try
{
Console.WriteLine($"点击坐标: {x}, {y}");
// 创建鼠标事件
var mouseEvent = new FBroSharpMouseEvent
{
x = x,
y = y,
modifiers = FBroSharpEventFlags.EVENTFLAG_LEFT_MOUSE_BUTTON
};
// 发送鼠标按下事件
browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, false, 1);
// 短暂延迟
Thread.Sleep(50);
// 发送鼠标释放事件
browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, true, 1);
Console.WriteLine("鼠标点击事件已发送");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"执行鼠标点击失败: {ex.Message}");
return false;
}
}2. 异步版本的元素点击
csharp
/// <summary>
/// 异步点击元素
/// </summary>
/// <param name="browser">浏览器实例</param>
/// <param name="selector">CSS选择器</param>
/// <param name="index">元素索引</param>
/// <returns>是否点击成功</returns>
public async Task<bool> ClickElementAsync(IFBroSharpBrowser browser, string selector, int index = 0)
{
return await Task.Run(() => ClickElementBySelector(browser, selector, index));
}方法详细说明
GetPoint 方法
csharp
/// <summary>
/// 取元素坐标
/// </summary>
/// <param name="querySelectorAll">CSS选择器</param>
/// <param name="index">索引:从0开始,选择器的第几个标签</param>
/// <param name="callback">回调函数:用于获取反馈结果的回调</param>
void GetPoint(string querySelectorAll, int index, FBroSharpJsCallback callback);参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
querySelectorAll | string | CSS选择器,如".class"、"#id"、"button" |
index | int | 元素索引,当选择器匹配多个元素时指定第几个 |
callback | FBroSharpJsCallback | 回调对象,用于接收坐标结果 |
SendMouseClickEvent 方法
csharp
/// <summary>
/// 发送鼠标点击事件
/// </summary>
/// <param name="ButtonType">鼠标按键类型</param>
/// <param name="mouserEvent">鼠标事件数据</param>
/// <param name="mouseUp">是否为释放事件</param>
/// <param name="clickCount">点击次数</param>
void SendMouseClickEvent(FBroSharpMouseButtonType ButtonType, FBroSharpMouseEvent mouserEvent, bool mouseUp, int clickCount);参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
ButtonType | FBroSharpMouseButtonType | 鼠标按键:MBT_LEFT/MBT_MIDDLE/MBT_RIGHT |
mouserEvent | FBroSharpMouseEvent | 鼠标事件数据(坐标、修饰键等) |
mouseUp | bool | false=按下事件,true=释放事件 |
clickCount | int | 点击次数,通常为1 |
枚举类型说明
FBroSharpMouseButtonType
csharp
public enum FBroSharpMouseButtonType
{
MBT_LEFT, // 左键
MBT_MIDDLE, // 中键(滚轮)
MBT_RIGHT // 右键
}FBroSharpEventFlags
csharp
public enum FBroSharpEventFlags
{
EVENTFLAG_NONE = 0, // 无修饰键
EVENTFLAG_CAPS_LOCK_ON = 1, // 大写锁定
EVENTFLAG_SHIFT_DOWN = 2, // Shift键按下
EVENTFLAG_CONTROL_DOWN = 4, // Ctrl键按下
EVENTFLAG_ALT_DOWN = 8, // Alt键按下
EVENTFLAG_LEFT_MOUSE_BUTTON = 0x10, // 左键按下
EVENTFLAG_MIDDLE_MOUSE_BUTTON = 0x20, // 中键按下
EVENTFLAG_RIGHT_MOUSE_BUTTON = 0x40, // 右键按下
EVENTFLAG_COMMAND_DOWN = 0x80, // Command键按下(Mac)
EVENTFLAG_NUM_LOCK_ON = 0x100, // 数字锁定
EVENTFLAG_IS_KEY_PAD = 0x200, // 来自小键盘
EVENTFLAG_IS_LEFT = 0x400, // 左侧键
EVENTFLAG_IS_RIGHT = 0x800, // 右侧键
EVENTFLAG_ALTGR_DOWN = 0x1000, // AltGr键按下
EVENTFLAG_IS_REPEAT = 0x2000 // 重复按键
}FBroSharpMouseEvent 结构
csharp
public struct FBroSharpMouseEvent
{
public int x; // X坐标
public int y; // Y坐标
public FBroSharpEventFlags modifiers; // 修饰键标志
}完整应用示例
1. 智能按钮点击器
csharp
public class SmartButtonClicker
{
private readonly IFBroSharpBrowser browser;
public SmartButtonClicker(IFBroSharpBrowser browser)
{
this.browser = browser;
}
/// <summary>
/// 尝试多个选择器点击按钮
/// </summary>
/// <param name="selectors">按钮选择器列表</param>
/// <param name="buttonName">按钮名称(用于日志)</param>
/// <returns>是否成功点击</returns>
public async Task<bool> TryClickButton(string[] selectors, string buttonName = "按钮")
{
foreach (string selector in selectors)
{
Console.WriteLine($"尝试点击{buttonName},选择器: {selector}");
if (await ClickElementAsync(browser, selector))
{
Console.WriteLine($"{buttonName}点击成功");
return true;
}
// 如果当前选择器失败,稍等后尝试下一个
await Task.Delay(500);
}
Console.WriteLine($"所有选择器都无法点击{buttonName}");
return false;
}
/// <summary>
/// 点击登录按钮示例
/// </summary>
/// <returns>是否成功</returns>
public async Task<bool> ClickLoginButton()
{
string[] loginSelectors = {
"#loginBtn", // ID选择器
".login-button", // 类选择器
"button[type='submit']", // 属性选择器
"input[value='登录']", // 值选择器
".btn-primary" // 主要按钮类
};
return await TryClickButton(loginSelectors, "登录按钮");
}
/// <summary>
/// 点击上传按钮示例(用于文件上传)
/// </summary>
/// <returns>是否成功</returns>
public async Task<bool> ClickUploadButton()
{
string[] uploadSelectors = {
"input[type='file']", // 文件输入框
".upload-button", // 上传按钮类
"#fileUpload", // 上传按钮ID
"button[data-action='upload']", // 数据属性选择器
".file-upload-btn" // 文件上传按钮类
};
return await TryClickButton(uploadSelectors, "上传按钮");
}
private async Task<bool> ClickElementAsync(IFBroSharpBrowser browser, string selector)
{
return await Task.Run(() => ClickElementBySelector(browser, selector));
}
private bool ClickElementBySelector(IFBroSharpBrowser browser, string selector, int index = 0)
{
// 此处使用前面定义的 ClickElementBySelector 方法
try
{
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainTianBiaoFrame().GetPoint(selector, index, callback);
if (callback.help != null && callback.help.IsValid)
{
callback.help.WaitEvent(10 * 1000);
if (callback.help.HavStringData())
{
string pointData = callback.help.GetStringData();
var coordinates = ParseCoordinates(pointData);
if (coordinates.HasValue)
{
return PerformMouseClick(browser, coordinates.Value.x, coordinates.Value.y);
}
}
}
return false;
}
catch
{
return false;
}
}
private (int x, int y)? ParseCoordinates(string pointData)
{
try
{
string[] parts = pointData.Split(',');
if (parts.Length >= 2)
{
float x = float.Parse(parts[0]);
float y = float.Parse(parts[1]);
return ((int)x, (int)y);
}
}
catch { }
return null;
}
private bool PerformMouseClick(IFBroSharpBrowser browser, int x, int y)
{
try
{
var mouseEvent = new FBroSharpMouseEvent
{
x = x,
y = y,
modifiers = FBroSharpEventFlags.EVENTFLAG_LEFT_MOUSE_BUTTON
};
browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, false, 1);
Thread.Sleep(50);
browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, true, 1);
return true;
}
catch
{
return false;
}
}
}2. 批量操作示例
csharp
public class BatchClickOperations
{
private readonly IFBroSharpBrowser browser;
public BatchClickOperations(IFBroSharpBrowser browser)
{
this.browser = browser;
}
/// <summary>
/// 批量点击列表项
/// </summary>
/// <param name="listSelector">列表选择器</param>
/// <param name="maxItems">最大点击数量</param>
/// <param name="interval">点击间隔(毫秒)</param>
/// <returns>成功点击的数量</returns>
public async Task<int> ClickListItems(string listSelector, int maxItems = 10, int interval = 1000)
{
int successCount = 0;
for (int i = 0; i < maxItems; i++)
{
Console.WriteLine($"点击第 {i + 1} 个列表项");
if (ClickElementBySelector(browser, listSelector, i))
{
successCount++;
Console.WriteLine($"第 {i + 1} 个列表项点击成功");
}
else
{
Console.WriteLine($"第 {i + 1} 个列表项点击失败或不存在");
break; // 如果某个项不存在,停止循环
}
// 等待间隔时间
if (i < maxItems - 1) // 最后一个不需要等待
{
await Task.Delay(interval);
}
}
Console.WriteLine($"批量点击完成,成功: {successCount}/{maxItems}");
return successCount;
}
/// <summary>
/// 点击所有匹配的复选框
/// </summary>
/// <param name="checkboxSelector">复选框选择器</param>
/// <returns>成功点击的数量</returns>
public async Task<int> CheckAllBoxes(string checkboxSelector)
{
return await ClickListItems(checkboxSelector, 50, 200); // 最多50个,间隔200ms
}
private bool ClickElementBySelector(IFBroSharpBrowser browser, string selector, int index = 0)
{
// 复用前面的实现
try
{
var callback = new FBroSharpJsCallbackSyn();
browser.GetMainTianBiaoFrame().GetPoint(selector, index, callback);
if (callback.help != null && callback.help.IsValid)
{
callback.help.WaitEvent(5 * 1000); // 减少等待时间提高效率
if (callback.help.HavStringData())
{
string pointData = callback.help.GetStringData();
string[] parts = pointData.Split(',');
if (parts.Length >= 2)
{
int x = (int)float.Parse(parts[0]);
int y = (int)float.Parse(parts[1]);
var mouseEvent = new FBroSharpMouseEvent
{
x = x,
y = y,
modifiers = FBroSharpEventFlags.EVENTFLAG_LEFT_MOUSE_BUTTON
};
browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, false, 1);
Thread.Sleep(30);
browser.SendMouseClickEvent(FBroSharpMouseButtonType.MBT_LEFT, mouseEvent, true, 1);
return true;
}
}
}
return false;
}
catch
{
return false;
}
}
}与VIP鼠标事件的对比
使用场景选择
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 点击已知选择器的元素 | 发送鼠标点击事件 | 自动定位,精确度高 |
| 点击固定坐标位置 | VIP鼠标事件 | 直接指定坐标,速度快 |
| 拖拽操作 | VIP鼠标事件 | 支持连续移动操作 |
| 复杂鼠标交互 | VIP鼠标事件 | 功能更完整 |
| 文件上传按钮点击 | 发送鼠标点击事件 | 绕过JavaScript限制 |
性能对比
| 方面 | 发送鼠标点击事件 | VIP鼠标事件 |
|---|---|---|
| 执行速度 | 较慢(需要获取坐标) | 快(直接操作) |
| 准确性 | 高(自动定位) | 中(依赖坐标准确性) |
| 易用性 | 高(选择器定位) | 中(需要手动获取坐标) |
| 功能完整性 | 低(仅基础点击) | 高(支持所有鼠标操作) |
最佳实践
1. 错误处理和重试
csharp
public async Task<bool> ClickWithRetry(IFBroSharpBrowser browser, string selector, int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
Console.WriteLine($"第 {attempt} 次尝试点击: {selector}");
if (ClickElementBySelector(browser, selector))
{
Console.WriteLine("点击成功");
return true;
}
if (attempt < maxRetries)
{
Console.WriteLine($"第 {attempt} 次尝试失败,等待重试...");
await Task.Delay(1000 * attempt); // 递增延迟
}
}
Console.WriteLine($"所有尝试都失败了: {selector}");
return false;
}2. 选择器优先级策略
csharp
public string[] GetPrioritySelectors(string elementType)
{
return elementType.ToLower() switch
{
"upload" => new[] {
"input[type='file']",
".upload-btn",
"#uploadButton",
"button[data-action='upload']"
},
"submit" => new[] {
"button[type='submit']",
"input[type='submit']",
".submit-btn",
"#submitButton"
},
"login" => new[] {
"#loginBtn",
".login-button",
"button[data-action='login']",
".btn-login"
},
_ => new[] { $"#{elementType}", $".{elementType}", elementType }
};
}3. 日志和调试
csharp
public class ClickLogger
{
public static void LogClick(string selector, int x, int y, bool success)
{
string status = success ? "成功" : "失败";
Console.WriteLine($"[鼠标点击] {selector} -> ({x}, {y}) - {status}");
}
public static void LogCoordinates(string selector, string coordinates)
{
Console.WriteLine($"[坐标获取] {selector} -> {coordinates}");
}
}注意事项
1. 坐标系统
- 坐标相对于浏览器视口的左上角
- 需要考虑页面滚动位置的影响
- 不同显示器DPI可能影响坐标精度
2. 时机控制
- 确保页面元素已完全加载
- 添加适当的延迟避免操作过快
- 使用回调机制等待异步操作完成
3. 兼容性
- 不同网站的元素结构可能不同
- 准备多个候选选择器提高成功率
- 定期验证选择器的有效性
通过这些方法和最佳实践,您可以实现稳定可靠的鼠标点击自动化操作。