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

C# 取得目前滑鼠游標圖樣並繪製到視窗

     編輯:關於C語言

需求場景

開發螢幕錄製、遠端桌面、教學示範軟體時,常需擷取目前滑鼠游標外觀並疊加到截圖或視訊串流中。Windows 不會自動將游標包含在 BitBlt 截圖裡,需額外呼叫 GetCursorInfo 取得游標句柄並自行繪製。

核心代碼

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class CursorCapture
{
    [StructLayout(LayoutKind.Sequential)]
    private struct CURSORINFO
    {
        public int cbSize;
        public int flags;
        public IntPtr hCursor;
        public Point ptScreenPos;
    }

    [DllImport("user32.dll")]
    private static extern bool GetCursorInfo(out CURSORINFO pci);

    private const int CURSOR_SHOWING = 0x00000001;

    /// 取得目前顯示的游標圖片與螢幕座標
    public static (Bitmap cursorBitmap, Point screenPos) GetCurrentCursor()
    {
        CURSORINFO ci = new CURSORINFO();
        ci.cbSize = Marshal.SizeOf(typeof(CURSORINFO));

        if (!GetCursorInfo(out ci)) return (null, Point.Empty);
        if ((ci.flags & CURSOR_SHOWING) != CURSOR_SHOWING) return (null, Point.Empty);

        // 從 HCURSOR 建立 Cursor 物件,再轉 Bitmap
        using (Cursor cursor = new Cursor(ci.hCursor))
        {
            // Cursor.Size 通常為 32x32,但也可能不同
            Bitmap bmp = new Bitmap(cursor.Size.Width, cursor.Size.Height);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                // 繪製游標至 Bitmap
                cursor.Draw(g, new Rectangle(Point.Empty, cursor.Size));
            }
            return (bmp, ci.ptScreenPos);
        }
    }

    /// 將游標疊加到指定影像(螢幕座標系)
    public static void DrawCursorOnImage(Image targetImage, Point imageOriginScreenPos)
    {
        var (cursorBmp, cursorScreenPos) = GetCurrentCursor();
        if (cursorBmp == null) return;

        // 計算游標在目標影像上的相對位置
        int x = cursorScreenPos.X - imageOriginScreenPos.X;
        int y = cursorScreenPos.Y - imageOriginScreenPos.Y;

        using (Graphics g = Graphics.FromImage(targetImage))
        {
            g.DrawImageUnscaled(cursorBmp, x, y);
        }
    }
}

WinForms 使用範例

public partial class MainForm : Form
{
    private Timer _timer;
    private PictureBox _picPreview;

    public MainForm()
    {
        _picPreview = new PictureBox { Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.Zoom };
        Controls.Add(_picPreview);

        _timer = new Timer { Interval = 100 };
        _timer.Tick += Timer_Tick;
        _timer.Start();
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        // 1. 截取全螢幕
        using (Bitmap screen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height))
        using (Graphics g = Graphics.FromImage(screen))
        {
            g.CopyFromScreen(Point.Empty, Point.Empty, screen.Size);
            
            // 2. 疊加游標
            CursorCapture.DrawCursorOnImage(screen, Point.Empty);
            
            // 3. 顯示預覽
            _picPreview.Image?.Dispose();
            _picPreview.Image = (Bitmap)screen.Clone();
        }
    }
}

技術細節補充

游標熱點

CURSORINFO.ptScreenPos 為游標熱點在螢幕上的座標。繪製時若直接用此座標,游標圖片左上角會對齊熱點,導致位置偏移。標準游標(.cur/.ani)內建熱點資訊,Cursor.Draw 會自動處理;若自行 DrawImage 需手動扣除熱點偏移(可用 GetIconInfo 取得)。

高 DPI 考量

  • 應用程式需設定 DPI Awareness (Per-Monitor v2 最佳)
  • CopyFromScreen、Cursor.Size 均回傳邏輯像素,座標系一致即可正確疊加
  • 若目標影像為實體像素(如直接給編碼器),需乘以縮放比例

系統游標 vs 自訂游標

GetCursorInfo 可捕捉所有游標(系統內建、應用程式 Cursor.Current 設定的自訂游標)。若應用程式使用 WPF Cursor 屬性,底層同樣會設定系統游標,故可正常擷取。

替代方案:Windows.Graphics.Capture (Win10 1903+)

// 現代 API 可選擇是否包含游標
var session = item.CreateCaptureSession();
session.IsCursorCaptureEnabled = true; // 自動包含游標,無需手動疊加

新專案建議優先使用 GraphicsCapture,效能更好、支援 HDR、跨程序邊界、自動處理游標與 DPI。

總結

透過 GetCursorInfo + Cursor.Draw 可在傳統 GDI 截圖流程中完美疊加滑鼠游標。核心關鍵在於:正確計算相對座標、處理游標熱點、釋放非託管資源。現代專案請直接啟用 GraphicsCaptureSession.IsCursorCaptureEnabled 簡化開發。

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