開發螢幕錄製、遠端桌面、教學示範軟體時,常需擷取目前滑鼠游標外觀並疊加到截圖或視訊串流中。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);
}
}
}
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 Awareness (Per-Monitor v2 最佳)CopyFromScreen、Cursor.Size 均回傳邏輯像素,座標系一致即可正確疊加GetCursorInfo 可捕捉所有游標(系統內建、應用程式 Cursor.Current 設定的自訂游標)。若應用程式使用 WPF Cursor 屬性,底層同樣會設定系統游標,故可正常擷取。
// 現代 API 可選擇是否包含游標
var session = item.CreateCaptureSession();
session.IsCursorCaptureEnabled = true; // 自動包含游標,無需手動疊加
新專案建議優先使用 GraphicsCapture,效能更好、支援 HDR、跨程序邊界、自動處理游標與 DPI。
透過 GetCursorInfo + Cursor.Draw 可在傳統 GDI 截圖流程中完美疊加滑鼠游標。核心關鍵在於:正確計算相對座標、處理游標熱點、釋放非託管資源。現代專案請直接啟用 GraphicsCaptureSession.IsCursorCaptureEnabled 簡化開發。