首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 呼叫 Win32 API 取得/設定游標座標與模擬點擊

     編輯:關於C語言

在 WinForms、WPF 或主控台應用程式中,若需取得螢幕絕對座標的游標位置、將游標移動至指定位置、或模擬滑鼠點擊,最直接的方式是透過 P/Invoke 呼叫 user32.dll 的 GetCursorPos、SetCursorPos、mouse_event(或 SendInput)。本文提供完整的 NativeMethods 封裝類別、座標轉換工具與安全模擬點擊範例。

P/Invoke 簽章定義

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("游標已還原");
    }
}

進階主題與注意事項

1. 多螢幕環境

  • Screen.PrimaryScreen 僅對應主螢幕。跨螢幕需使用 Screen.AllScreens 判斷座標落在哪顆螢幕,並以該螢幕 Bounds 計算正規化座標。
  • SetCursorPos 接受虛擬螢幕座標(可為負值),直接傳入實體像素即可,無需正規化。

2. DPI 感知

若應用程式未設定 DPI Awareness(Per-Monitor DPI Aware),在高 DPI 螢幕上座標會被虛擬化縮放,導致實際點擊位置偏移。解決方式:

  • 在 app.manifest 加入 <dpiAware>True/PM</dpiAware>。
  • 或呼叫 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)(Windows 10 1703+)。

3. SendInput vs mouse_event

項目mouse_eventSendInput
API 狀態Deprecated(仍可用)推薦
輸入類型僅滑鼠滑鼠/鍵盤/觸控/筆
同步性非同步佇列同步插入輸入流,順序保證
安全性易被攔截/阻擋較難被攔截,支援 UIPI

新專案請優先使用 SendInput。

4. 權限與 UIPI

  • 若目標視窗以系統管理員權限執行,而發送端為一般權限,UIPI (User Interface Privilege Isolation) 會阻擋輸入注入。
  • 解法:發送端同樣以管理員權限執行,或使用 ChangeWindowMessageFilterEx 允許特定訊息(不建議)。

5. 測試建議

  • 先在記事本或瀏覽器空白頁測試座標與點擊行為。
  • 加入 Thread.Sleep(100~300) 觀察游標移動軌跡,避免過快導致目標應用程式未處理完訊息。
  • 記錄 Marshal.GetLastWin32Error() 檢查 API 呼叫失敗原因。

結語

透過 GetCursorPos/SetCursorPos 取得與設定游標座標,搭配 SendInput 進行安全的滑鼠模擬,是 C# 操作作業系統層級輸入的標準做法。請務必處理 DPI 感知、多螢幕座標換算與權限隔離,並在自動化測試或 RPA 案例中謹慎控制執行順序與延遲,以確保穩定可靠。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved