專案將 .cur/.ani 游標檔案嵌入資源,透過 new Cursor(new MemoryStream(Resource.MyCursor)) 載入。測試發現:黑白單色游標總能載入,但含色彩/透明通道的游標「時而替換成功,時而替換不成功」,行為極不穩定。
.NET Cursor 類別的建構函數 (Cursor(Stream)) 底層調用 Win32 CreateIconFromResourceEx,**僅支援單色 (黑白) 游標格式** 。彩色游標雖然檔案格式合法,但傳入此建構函數會靜默失敗 (回傳預設游標或拋出例外),且行為隨作業系統版本、桌面合成 (DWM) 狀態不同而異,造成「時而成功」的假象。
Win32 API LoadCursorFromFile 完整支援彩色、動畫、高解析度游標。策略:將資源流寫入暫存檔,再呼叫此 API 取得 HCURSOR,最後包裝為 Cursor 物件。
public static class CursorHelper {
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr LoadCursorFromFile(string fileName);
public static Cursor LoadResourceCursor(byte[] cursorData) {
// 1. 寫入暫存檔 (副檔名 .cur 或 .ani 均可)
string tempPath = Path.GetTempFileName();
try {
File.WriteAllBytes(tempPath, cursorData);
// 2. 呼叫 Win32 API 載入
IntPtr hCursor = LoadCursorFromFile(tempPath);
if (hCursor == IntPtr.Zero) {
int err = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(err, "LoadCursorFromFile 失敗");
}
// 3. 包裝為 .NET Cursor (不擁有控制代碼所有權,不會自動銷毀 HCURSOR)
return new Cursor(hCursor);
} finally {
// 4. 清理暫存檔 (Cursor 已複製內部資源,檔案可刪)
try { File.Delete(tempPath); } catch { /* 忽略 */ }
}
}
}
// 載入嵌入資源
byte[] cursorBytes = Properties.Resources.MyColorCursor;
Cursor myCursor = CursorHelper.LoadResourceCursor(cursorBytes);
// 套用至控件
myControl.Cursor = myCursor;
Path.GetTempFileName() 保證唯一性,避免並發衝突。IntPtr.Zero 並拋出 Win32Exception 獲取具體錯誤碼 (如檔案格式錯誤、找不到檔案)。Cursor 建構函數 new Cursor(hCursor) **不會取得 HCURSOR 擁有權**,銷毀 Cursor 物件不會銷毀系統游標控制代碼。系統會在進程結束時自動回收。若需精確控制生命週期,可自行 DestroyCursor (需 P/Invoke)。Cursor 實例,避免重複 I/O 與 P/Invoke 開銷。Cursor 類別的 Cursor(Stream) 建構函數 (WPF 內部已處理彩色游標),或使用 XAML <Cursor> 資源。本文方案針對 WinForms / 核心 .NET Framework 通用場景。.NET Cursor(Stream) 建構函數對彩色游標支援缺陷是長久存在的已知限制。透過 LoadCursorFromFile 暫存檔中轉,可完美支援所有 Windows 原生游標格式 (含動畫 .ani、高 DPI、Alpha 通道),解決「時而失效」的詭異行為,是 WinForms 開發中處理自定義游標的標準最佳實踐。