在 WinForms、WPF 或主控台應用程式中,若需取得螢幕絕對座標的游標位置、將游標移動至指定位置、或模擬滑鼠點擊,最直接的方式是透過 P/Invoke 呼叫 user32.dll 的 GetCursorPos、SetCursorPos、mouse_event(或 SendInput)。本文提供完整的 NativeMethods 封裝類別、座標轉換工具與安全模擬點擊範例。
using System;
using System.Runtime.InteropServices;
internal static class NativeMethods
{
// 結構對應 POINT { LONG x, y; }
[StructLayout(LayoutKind.Sequential)]
public struct POINT { public int X; public int Y; }
// 取得游標螢幕座標
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT lpPoint);
// 設定游標螢幕座標
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetCursorPos(int X, int Y);
// 滑鼠事件旗標
[Flags]
public enum MouseEventFlags : uint
{
MOVE = 0x0001,
LEFTDOWN = 0x0002,
LEFTUP = 0x0004,
RIGHTDOWN = 0x0008,
RIGHTUP = 0x0010,
MIDDLEDOWN = 0x0020,
MIDDLEUP = 0x0040,
ABSOLUTE = 0x8000 // 座標為 0~65535 正規化
}
// 舊版 mouse_event(較簡單但已棄用建議改用 SendInput)
[DllImport("user32.dll", SetLastError = true)]
public static extern void mouse_event(
MouseEventFlags dwFlags, uint dx, uint dy, uint dwData, IntPtr dwExtraInfo);
// 推薦:SendInput(支援鍵盤/滑鼠/硬體輸入,更安全)
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public uint Type; // 0=MOUSE, 1=KEYBOARD, 2=HARDWARE
public MOUSEINPUT mi;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx, dy;
public uint mouseData;
public MouseEventFlags dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
}
Win32 絕對座標為 0~65535 對應螢幕寬高,需換算:
public static class CursorHelper
{
// 取得目前游標座標(螢幕絕對像素)
public static Point GetCursorPosition()
{
NativeMethods.GetCursorPos(out var pt);
return new Point(pt.X, pt.Y);
}
// 移動游標至螢幕絕對像素
public static void SetCursorPosition(Point screenPos)
=> NativeMethods.SetCursorPos(screenPos.X, screenPos.Y);
// 將螢幕像素轉為 0~65535 正規化座標(供 mouse_event ABSOLUTE 使用)
public static (uint dx, uint dy) ToNormalized(Point screenPos)
{
int w = Screen.PrimaryScreen.Bounds.Width;
int h = Screen.PrimaryScreen.Bounds.Height;
uint dx = (uint)(screenPos.X * 65535 / w);
uint dy = (uint)(screenPos.Y * 65535 / h);
return (dx, dy);
}
// 使用 SendInput 模擬左鍵單點(推薦)
public static void LeftClick(Point? screenPos = null)
{
if (screenPos.HasValue) SetCursorPosition(screenPos.Value);
var (dx, dy) = ToNormalized(GetCursorPosition());
var inputs = new NativeMethods.INPUT[2];
inputs[0] = new NativeMethods.INPUT
{
Type = 0, // MOUSE
mi = new NativeMethods.MOUSEINPUT { dx = (int)dx, dy = (int)dy,
dwFlags = NativeMethods.MouseEventFlags.ABSOLUTE | NativeMethods.MouseEventFlags.LEFTDOWN }
};
inputs[1] = new NativeMethods.INPUT
{
Type = 0,
mi = new NativeMethods.MOUSEINPUT { dx = (int)dx, dy = (int)dy,
dwFlags = NativeMethods.MouseEventFlags.ABSOLUTE | NativeMethods.MouseEventFlags.LEFTUP }
};
NativeMethods.SendInput(2, inputs, Marshal.SizeOf());
}
// 使用 SendInput 模擬左鍵雙擊
public static void DoubleClick(Point? screenPos = null)
{
LeftClick(screenPos);
System.Threading.Thread.Sleep(50); // 視系統 DoubleClickSpeed 調整
LeftClick(null);
}
}
class Program
{
static void Main()
{
// 1. 記錄原始位置
Point original = CursorHelper.GetCursorPosition();
Console.WriteLine($"原始座標:{original}");
// 2. 移動至 (500, 300) 並單擊
Point target = new Point(500, 300);
CursorHelper.SetCursorPosition(target);
System.Threading.Thread.Sleep(200); // 給 UI 反應時間
CursorHelper.LeftClick();
Console.WriteLine($"已在 {target} 單擊");
// 3. 移動至 (600, 400) 並雙擊
target = new Point(600, 400);
CursorHelper.DoubleClick(target);
Console.WriteLine($"已在 {target} 雙擊");
// 4. 還原游標
CursorHelper.SetCursorPosition(original);
Console.WriteLine("游標已還原");
}
}
Screen.PrimaryScreen 僅對應主螢幕。跨螢幕需使用 Screen.AllScreens 判斷座標落在哪顆螢幕,並以該螢幕 Bounds 計算正規化座標。SetCursorPos 接受虛擬螢幕座標(可為負值),直接傳入實體像素即可,無需正規化。若應用程式未設定 DPI Awareness(Per-Monitor DPI Aware),在高 DPI 螢幕上座標會被虛擬化縮放,導致實際點擊位置偏移。解決方式:
app.manifest 加入 <dpiAware>True/PM</dpiAware>。SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)(Windows 10 1703+)。| 項目 | mouse_event | SendInput |
|---|---|---|
| API 狀態 | Deprecated(仍可用) | 推薦 |
| 輸入類型 | 僅滑鼠 | 滑鼠/鍵盤/觸控/筆 |
| 同步性 | 非同步佇列 | 同步插入輸入流,順序保證 |
| 安全性 | 易被攔截/阻擋 | 較難被攔截,支援 UIPI |
新專案請優先使用 SendInput。
ChangeWindowMessageFilterEx 允許特定訊息(不建議)。Thread.Sleep(100~300) 觀察游標移動軌跡,避免過快導致目標應用程式未處理完訊息。Marshal.GetLastWin32Error() 檢查 API 呼叫失敗原因。透過 GetCursorPos/SetCursorPos 取得與設定游標座標,搭配 SendInput 進行安全的滑鼠模擬,是 C# 操作作業系統層級輸入的標準做法。請務必處理 DPI 感知、多螢幕座標換算與權限隔離,並在自動化測試或 RPA 案例中謹慎控制執行順序與延遲,以確保穩定可靠。