1. VB.NET 鼠标点击程序代码到底怎么写:从 Win32 API 到 SendInput 的完整落地
VB.NET 鼠标点击程序代码,说白了就是让桌面程序替你去点鼠标——指定坐标、按下左键、抬起左键,必要时再拖一段距离。它适合做自动化测试、批量表单填写、重复性桌面操作脚本,也适合给内部工具加一个"一键执行"按钮。很多人第一次写会直接抄一段mouse_event的代码,跑起来发现坐标偏了、点击没反应、或者程序卡死,问题基本都出在坐标系、DPI 缩放和线程阻塞这三件事上。
我这次把两种主流方案都写清楚:一种是经典的SetCursorPos+mouse_event,代码短、上手快;另一种是SendInput,结构体多一点,但更贴近系统底层、兼容性更好,尤其在多显示器和高 DPI 环境下更稳。两种方案我都会给出可直接复制的 VB.NET 函数,并且演示怎么用 TaoToken 的统一 Key 调 AI 接口来辅助生成和纠错这些代码——比如你写完SendInput结构体不确定字段顺序对不对,或者报错reading choices不知道怎么改,都可以让 AI 帮你过一遍。
先明确一个概念:VB.NET 里操作鼠标不是靠 .NET 类库,而是通过 P/Invoke 调用user32.dll里的 Win32 API。SetCursorPos负责把光标移到屏幕绝对坐标,mouse_event和SendInput负责模拟按键事件。屏幕坐标原点在左上角,X 向右增大,Y 向下增大,单位是像素。听起来简单,但如果你在 125% 或 150% 缩放的显示器上跑,拿到的坐标和实际点击位置会对不上,这是最常见的坑,后面第 5 节会专门讲。
这篇文章的节奏是这样:先讲清楚问题场景和两种方案的取舍,再讲怎么用 TaoToken 拿到统一 Key 并配好环境,然后给出完整可复制的配置和代码,接着验证点击是否真的生效,再排查几个高频报错,最后给一个接入文档和 API Keys 的入口。全程按"能跟着做"来写,不堆概念。
2. 用 TaoToken 统一 Key 接入 AI 辅助生成与调试 VB.NET 点击代码
写 Win32 API 调用最烦的不是逻辑,是细节:MOUSEEVENTF_LEFTDOWN的值是&H2还是2,INPUT结构体里dwType和mi的嵌套顺序,DllImport的CharSet要不要加。这些细节记不住很正常,让 AI 帮你生成一版再对照修改,效率高很多。TaoToken 在这里的作用是提供一个统一的 API Key,你不用为不同模型分别申请和切换 Key,一个 Key 就能调对话模型来生成代码、解释报错、做代码审查。
TaoToken 是什么:它是一个统一的大模型 API 接入服务,把多个模型的调用收敛到一套 Base URL 和 Key 上。能做什么:你可以用它调模型对话来生成 VB.NET 代码片段、让它解释SendInput结构体、或者把一段报错贴进去让它给修复建议。适合谁:适合正在写桌面自动化、又不想在多个平台之间来回折腾 Key 的开发者。
接入前你需要准备三样东西,这也是后面所有配置的基础,我把它叫"三件套":
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求的统一入口,不要加 UTM 参数 |
| API Key | 在控制台创建 | 形如sk-开头的一串字符,只显示一次,记得保存 |
| Model ID | 按需选择 | 例如对话类模型 ID,填在请求体的model字段 |
拿 Key 的路径很直接:打开控制台,进 API Keys 页面创建一个新 Key,复制保存。控制台地址是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console,API Keys 页面是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys。创建完 Key 之后,如果你想先在网页上试试模型能不能正常回话,可以去模型对话页面https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat发一条消息验证。
这里要提醒一句:Key 是敏感信息,不要硬编码进 VB.NET 源码然后提交到仓库。建议放在环境变量或者单独的配置文件里,代码里读配置。下面第 3 节我会给出具体的读取方式。
环境准备方面,你需要在 Visual Studio 里新建一个 Windows Forms App (.NET Framework) 项目,目标框架选 4.7.2 或更高都行。项目建好后,Imports System.Runtime.InteropServices是必须的,因为 P/Invoke 依赖这个命名空间。如果你用的是 .NET 6/7/8 的 WinForms,DllImport的写法基本一致,但要注意SendInput的INPUT结构体在 64 位下的大小对齐问题,这个后面会讲。
为什么用 AI 辅助而不是纯手写:SendInput的INPUT联合体在 VB.NET 里要用StructLayout(LayoutKind.Explicit)加FieldOffset来模拟,字段偏移写错一个,整个调用就静默失败——不报错,但鼠标不动。这种问题靠肉眼很难查,把结构体贴给 AI 让它核对偏移量,几分钟就能定位。这就是统一 Key 接入的实际价值:不是替代你写代码,而是帮你把容易错的细节过一遍。
3. 可复制的 VB.NET 鼠标点击配置与代码:SetCursorPos 与 SendInput 双方案
这一节是核心,给出完整可复制的代码。先讲配置,再讲两种方案的实现。
3.1 把 Base URL、Key、Model ID 写进配置文件
在项目根目录建一个appsettings.json(.NET Framework 项目可以手动读,或者用My.Settings)。内容如下:
{ "TaoToken": { "BaseUrl": "https://taotoken.net/api", "ApiKey": "sk-你的Key粘贴在这里", "ModelId": "你的模型ID" } }如果你不想引入 JSON 解析库,用最简单的My.Settings也行,在项目属性里加三个字符串设置项:TaoTokenBaseUrl、TaoTokenApiKey、TaoTokenModelId。代码里这样读:
Dim baseUrl As String = My.Settings.TaoTokenBaseUrl Dim apiKey As String = My.Settings.TaoTokenApiKey Dim modelId As String = My.Settings.TaoTokenModelId注意 Base URL 结尾不要带斜杠,请求路径拼接时统一用/v1/chat/completions这种形式。Key 不要写死在Form1.vb里。
3.2 方案一:SetCursorPos + mouse_event 实现点击
这是最经典的写法,代码短。先声明 API:
Imports System.Runtime.InteropServices Imports System.Threading Public Class MouseClickHelper <DllImport("user32.dll")> Private Shared Function SetCursorPos(ByVal x As Integer, ByVal y As Integer) As Boolean End Function <DllImport("user32.dll")> Private Shared Sub mouse_event(ByVal dwFlags As Integer, ByVal dx As Integer, ByVal dy As Integer, ByVal dwData As Integer, ByVal dwExtraInfo As IntPtr) End Sub Private Const MOUSEEVENTF_LEFTDOWN As Integer = &H2 Private Const MOUSEEVENTF_LEFTUP As Integer = &H4 Private Const MOUSEEVENTF_RIGHTDOWN As Integer = &H8 Private Const MOUSEEVENTF_RIGHTUP As Integer = &H10 ''' <summary> ''' 在指定屏幕坐标执行一次左键单击 ''' </summary> Public Shared Sub LeftClick(ByVal x As Integer, ByVal y As Integer, Optional ByVal delayMs As Integer = 50) SetCursorPos(x, y) Thread.Sleep(delayMs) mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, IntPtr.Zero) Thread.Sleep(delayMs) mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, IntPtr.Zero) End Sub ''' <summary> ''' 右键单击 ''' </summary> Public Shared Sub RightClick(ByVal x As Integer, ByVal y As Integer, Optional ByVal delayMs As Integer = 50) SetCursorPos(x, y) Thread.Sleep(delayMs) mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, IntPtr.Zero) Thread.Sleep(delayMs) mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, IntPtr.Zero) End Sub End Class调用方式:
MouseClickHelper.LeftClick(500, 300)这里有个关键点:mouse_event的dwExtraInfo参数类型我用的是IntPtr,而不是原示例里的Integer。在 64 位系统上,用Integer会有隐患,IntPtr更安全。另外Thread.Sleep的延迟不要省,按下和抬起之间至少留 30 到 50 毫秒,否则某些程序会识别不到点击。
3.3 方案二:SendInput 实现点击(推荐)
SendInput比mouse_event更底层,微软官方也建议新代码用SendInput。它需要定义INPUT、MOUSEINPUT结构体:
Imports System.Runtime.InteropServices Imports System.Threading Public Class SendInputHelper <StructLayout(LayoutKind.Sequential)> Private Structure MOUSEINPUT Public dx As Integer Public dy As Integer Public mouseData As UInteger Public dwFlags As UInteger Public time As UInteger Public dwExtraInfo As IntPtr End Structure <StructLayout(LayoutKind.Sequential)> Private Structure INPUT Public type As UInteger Public mi As MOUSEINPUT End Structure <DllImport("user32.dll", SetLastError:=True)> Private Shared Function SendInput(ByVal nInputs As UInteger, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As UInteger End Function <DllImport("user32.dll")> Private Shared Function SetCursorPos(ByVal x As Integer, ByVal y As Integer) As Boolean End Function Private Const INPUT_MOUSE As UInteger = 0 Private Const MOUSEEVENTF_LEFTDOWN As UInteger = &H2 Private Const MOUSEEVENTF_LEFTUP As UInteger = &H4 Private Const MOUSEEVENTF_ABSOLUTE As UInteger = &H8000 Public Shared Sub LeftClick(ByVal x As Integer, ByVal y As Integer, Optional ByVal delayMs As Integer = 50) SetCursorPos(x, y) Thread.Sleep(delayMs) Dim inputs(1) As INPUT inputs(0).type = INPUT_MOUSE inputs(0).mi.dwFlags = MOUSEEVENTF_LEFTDOWN inputs(1).type = INPUT_MOUSE inputs(1).mi.dwFlags = MOUSEEVENTF_LEFTUP Dim sent As UInteger = SendInput(2UI, inputs, Marshal.SizeOf(GetType(INPUT))) If sent <> 2UI Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If End Sub End Class调用和方案一一样:SendInputHelper.LeftClick(500, 300)。
两种方案的取舍我用表格对比一下:
| 对比项 | SetCursorPos + mouse_event | SendInput |
|---|---|---|
| 代码量 | 少 | 中等 |
| 兼容性 | 一般,部分新程序不响应 | 好,官方推荐 |
| 多显示器 | 需额外处理 | 支持绝对坐标 |
| 报错可查 | 基本静默 | 可通过 GetLastWin32Error 查 |
| 推荐场景 | 快速脚本 | 生产级自动化 |
如果你只是临时写个脚本点几下,方案一够用;如果要做长期运行的自动化工具,直接上方案二。
3.4 用 TaoToken 调 AI 生成和纠错代码
拿到 Key 之后,你可以用 VB.NET 的HttpClient发请求。下面是一个最小可用的调用函数:
Imports System.Net.Http Imports System.Text Imports Newtonsoft.Json.Linq Public Class AiHelper Public Shared Async Function AskAsync(prompt As String) As Task(Of String) Dim baseUrl As String = My.Settings.TaoTokenBaseUrl Dim apiKey As String = My.Settings.TaoTokenApiKey Dim modelId As String = My.Settings.TaoTokenModelId Using client As New HttpClient() client.DefaultRequestHeaders.Add("Authorization", "Bearer " & apiKey) Dim body As New JObject() body("model") = modelId Dim messages As New JArray() messages.Add(New JObject() From { {"role", "user"}, {"content", prompt} }) body("messages") = messages Dim content As New StringContent(body.ToString(), Encoding.UTF8, "application/json") Dim resp As HttpResponseMessage = Await client.PostAsync(baseUrl & "/v1/chat/completions", content) Dim text As String = Await resp.Content.ReadAsStringAsync() If Not resp.IsSuccessStatusCode Then Throw New Exception("请求失败:" & resp.StatusCode & " " & text) End If Dim json As JObject = JObject.Parse(text) Return json("choices")(0)("message")("content").ToString() End Using End Function End Class用法示例,让它帮你核对SendInput结构体:
Dim reply As String = Await AiHelper.AskAsync( "下面这段 VB.NET 的 SendInput 结构体定义,字段偏移和类型是否正确?" & vbCrLf & "MOUSEINPUT: dx Integer, dy Integer, mouseData UInteger, dwFlags UInteger, time UInteger, dwExtraInfo IntPtr")返回的内容会告诉你字段顺序和类型是否匹配。如果它指出问题,你直接改代码就行。这就是统一 Key 接入的用法:把容易错的细节交给模型核对,你负责集成和验证。
4. 验证点击坐标与事件触发:怎么确认代码真的点到了
代码写完不代表能跑通,必须验证。验证分三步:先确认坐标对不对,再确认点击事件有没有触发,最后确认目标程序有没有响应。
4.1 验证坐标:先读再点
写一个读当前鼠标坐标的函数,用GetCursorPos:
<StructLayout(LayoutKind.Sequential)> Public Structure POINTAPI Public X As Integer Public Y As Integer End Structure <DllImport("user32.dll")> Public Shared Function GetCursorPos(ByRef lpPoint As POINTAPI) As Boolean End Function在窗体上放一个Timer,间隔 100 毫秒刷新一次,把坐标显示在Label上:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick Dim p As POINTAPI GetCursorPos(p) Label1.Text = "X=" & p.X & " Y=" & p.Y End Sub运行程序,把鼠标移到你想点击的目标位置,记下Label上的坐标。这个坐标就是你要传给LeftClick的值。注意:如果你在缩放不是 100% 的显示器上,这里读到的坐标是物理像素坐标,而SetCursorPos接受的也是物理像素坐标,两者一致,所以直接用没问题。但如果你用Control.MousePosition读,拿到的可能是逻辑坐标,会偏,所以统一用GetCursorPos。
4.2 验证点击事件:用一个测试窗体接收
建一个测试用的 WinForms 窗体,上面放一个按钮,按钮的Click事件里弹一个MessageBox。然后用你的点击代码去点这个按钮的坐标。如果弹窗出现,说明点击生效。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click MessageBox.Show("点击已触发") End Sub先手动点一次确认按钮正常,再用代码点。代码点的时候,把按钮的屏幕坐标算出来:
Dim screenPos As Point = Button1.PointToScreen(New Point(Button1.Width \ 2, Button1.Height \ 2)) SendInputHelper.LeftClick(screenPos.X, screenPos.Y)PointToScreen把控件内坐标转成屏幕坐标,取中心点更稳。如果弹窗出现,说明整条链路通了。
4.3 验证目标程序响应:用记事本做端到端测试
打开记事本,把窗口移到固定位置,用代码点击记事本的编辑区,然后检查光标有没有落进去。更严格的测试是点击菜单项,比如点"格式"菜单,看菜单有没有展开。这一步能验证点击是否被目标程序真正接收,而不只是被系统记录。
如果前两步都通过、第三步失败,通常是目标程序以管理员权限运行,而你的程序不是,Windows 的 UIPI 机制会拦截跨权限的输入。解决办法是让你的程序也以管理员身份运行,在app.manifest里设置requestedExecutionLevel level="requireAdministrator"。
4.4 用 AI 辅助排查验证失败
如果点击没反应,把现象描述给 AI,比如"VB.NET SendInput 调用返回 2,但目标程序没响应,目标程序是管理员权限",它会提示你检查 UIPI 和权限。这种排查用统一 Key 调模型很快,不用自己翻文档。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
这一节列几个高频报错和对应处理。这些报错分两类:一类是调 AI 接口时的,一类是鼠标点击本身的。
5.1 401 Unauthorized
调 TaoToken 接口返回 401,说明 Key 不对或没带上。检查三件事:请求头里Authorization是不是Bearer加 Key,中间有一个空格;Key 有没有复制完整,前后有没有多余空格;Key 是不是已经被删除或过期。在控制台重新创建一个 Key 再试。注意 Base URL 用https://taotoken.net/api,不要拼错。
5.2 local proxy failed
这个报错通常出现在你本地配了代理,但代理没启动或者端口不对。检查你的HttpClient有没有走系统代理,如果不需要代理,在HttpClientHandler里设UseProxy = False。另外确认你的网络环境能正常访问taotoken.net,用浏览器打开模型对话页面试试能不能加载。
5.3 reading choices 报错
这个报错一般是你解析返回 JSON 时,choices字段不存在或者结构不对。常见原因:请求失败但你没检查状态码就直接解析;或者模型返回的是流式格式,你按非流式解析。处理办法是先打印原始返回文本,确认结构,再取choices(0).message.content。如果返回体里是error字段,说明请求本身有问题,先解决错误再解析。
5.4 OAuth 相关报错
如果你用的是某些需要 OAuth 授权的客户端工具,报 OAuth 错误通常是回调地址或 token 过期。这类工具一般支持直接填 API Key 的方式,优先用 Key 方式接入,避免 OAuth 流程。如果必须用 OAuth,检查系统时间是否准确,时间偏差过大会导致 token 校验失败。
5.5 鼠标点击本身的报错
SendInput返回 0 而不是 2,说明调用失败。用Marshal.GetLastWin32Error()拿错误码。常见错误码 5 是权限不足,错误码 87 是参数错误(结构体大小不对)。结构体大小用Marshal.SizeOf(GetType(INPUT))算,64 位下应该是 40 字节,32 位下是 28 字节。如果算出来不对,检查MOUSEINPUT里dwExtraInfo是不是IntPtr。
另一个高频问题是点击坐标偏移。如果你在 150% 缩放的屏幕上,用Control.MousePosition拿到的坐标和SetCursorPos需要的坐标不一致。统一用GetCursorPos读、SetCursorPos写,两者都是物理像素,就不会偏。
5.6 三件套检查清单
如果你用 Cline、CC Switch 或 Codex 这类工具接入,配置里必须写全三件套:Base URL 填https://taotoken.net/api,Key 填你创建的 Key,Model ID 填你要用的模型。少任何一个都会报错。Codex 的auth.json里对应字段是base_url、api_key、model,CC Switch 里是baseUrl、apiKey、model,Cline 的 MCP 配置里是baseUrl、apiKey、modelId。字段名不同,值是一样的。
6. 长期做桌面自动化,用 Coding Plan 更省心
如果你只是偶尔写一段点击代码,按上面的步骤配好 Key 就够了。但如果你要长期做桌面自动化、批量脚本、或者把 AI 辅助集成到日常开发流程里,每次手动管 Key、切模型会比较烦。TaoToken 的 Coding Plan 就是为这种场景准备的,它把编码相关的调用打包成一个长期可用的方案,适合持续写代码、跑 Agent 的开发者。入口在https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan。
回到代码本身,最后给你一个实用技巧:把LeftClick和RightClick封装成带重试的版本。因为桌面程序响应有快有慢,单次点击可能落空。加一个重试循环,点完检查目标状态,没变就再点一次,最多三次。这个模式在自动化里很常见,比单纯加长Thread.Sleep更可靠。
Public Shared Sub LeftClickWithRetry(ByVal x As Integer, ByVal y As Integer, ByVal checkFunc As Func(Of Boolean), Optional ByVal maxRetry As Integer = 3) For i As Integer = 1 To maxRetry LeftClick(x, y) Thread.Sleep(300) If checkFunc() Then Return Next Throw New Exception("点击后状态未变化,已重试 " & maxRetry & " 次") End SubcheckFunc是你自己写的判断函数,比如检查某个窗口标题变了、某个文本框有内容了。这样点击就不是盲点,而是有反馈的闭环。写自动化最怕的就是"以为点到了其实没点到",加个检查函数能省很多调试时间。
接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc,API Keys 在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys。先把 Key 建好,把上面的AiHelper跑通,再回头调你的点击代码,顺序别反。