C#自定义鼠标光标:WinForm与WPF编程实现

今天早睡
今天早睡
发布时间:2026-10-15 | 4人浏览过
原创

在C# .NET桌面应用开发中,标准箭头光标往往难以满足个性化设计需求。本文详细解析如何在WinForm和WPF项目中自定义鼠标光标,通过底层API调用将绘制的图形转化为交互光标,涵盖从结构定义到热座标点设定的完整流程。

结果与目标

自定义光标的核心在于将位图数据转换为Windows系统可识别的光标句柄。无论是WinForm还是WPF,最终都需要通过System.Windows.Forms.Cursor或System.Windows.Input.Cursor对象赋值给界面的Cursor属性。关键在于正确设置光标的“热点”(Hotspot),即用户点击时实际生效的像素坐标。

准备条件

  • 运行环境:.NET Framework 或 .NET Core/.NET 5+(支持System.Drawing.Common)。注意:在跨平台场景下,System.Drawing需谨慎使用。
  • 基础控件:WinForm需继承自System.Windows.Forms.Form或Control;WPF需使用System.Windows.UIElement或具体控件。
  • 依赖库:需要引用User32.dll以访问底层API。

WinForm程序实现步骤

第1步:理解光标底层结构ICONINFO

Windows系统中,光标的创建依赖于ICONINFO结构体。它定义了光标的位图掩码、颜色信息以及热点坐标。我们需要在代码中定义与之匹配的C#结构:

public struct IconInfo
{
    public bool fIcon;      // false表示光标(true表示图标)
    public int xHotspot;    // X轴热点坐标
    public int yHotspot;    // Y轴热点坐标
    public IntPtr hbmMask;  // 掩码位图句柄
    public IntPtr hbmColor; // 颜色位图句柄
}

第2步:封装NativeMethods获取API接口

使用DllImport引入user32.dll中的关键函数:CreateIconIndirect用于间接创建图标/光标,GetIconInfo用于获取现有图标信息作为模板。

static class NativeMethods
{
    [DllImport("user32.dll")]
    public static extern IntPtr CreateIconIndirect(ref IconInfo icon);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
}

第3步:编写CreateCursor辅助方法

该方法接收一个Bitmap对象和热点坐标。内部逻辑是先获取Bitmap的图标句柄,填充ICONINFO结构,然后调用CreateIconIndirect生成新光标句柄,最后封装为System.Windows.Forms.Cursor对象。

public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot)
{
    var icon = new NativeMethods.IconInfo
    {
        xHotspot = xHotSpot,
        yHotspot = yHotSpot,
        fIcon = false
    };

    // 获取位图的图标信息
    NativeMethods.GetIconInfo(bmp.GetHicon(), ref icon);
    
    // 创建新的间接光标
    return new Cursor(NativeMethods.CreateIconIndirect(ref icon));
}

第4步:测试与应用

在窗体加载或按钮事件中,创建一个Graphics实例绘制简单图形(如红色矩形),然后应用自定义光标:

using (Bitmap bitmap = new Bitmap(21, 26))
using (Graphics g = Graphics.FromImage(bitmap))
{
    g.DrawRectangle(Pens.Red, 0, 0, 20, 25);
    this.Cursor = CursorHelper.CreateCursor(bitmap, 3, 3); // 设置热点为(3,3)
}

WPF程序实现步骤

WPF的光标处理稍显复杂,因为其渲染基于DirectX且涉及独立的资源池。此外,WPF默认不直接接受System.Drawing.Bitmap,需要额外的转换层。

第1步:完善NativeMethods与安全句柄

为了内存安全,需引入SafeHandleZeroOrMinusOneIsInvalid来管理光标句柄的生命周期,防止内存泄漏。同时添加DestroyIcon用于释放资源。

[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public SafeIconHandle() : base(true) { }

    protected override bool ReleaseHandle()
    {
        return NativeMethods.DestroyIcon(handle);
    }
}

static class NativeMethods
{
    // ... 其他声明 ...
    
    [DllImport("user32.dll")]
    public static extern SafeIconHandle CreateIconIndirect(ref IconInfo icon);

    [DllImport("user32.dll")]
    public static extern bool DestroyIcon(IntPtr hIcon);
}

第2步:实现InternalCreateCursor核心逻辑

此方法与WinForm版本类似,但返回值类型为CursorInteropHelper.Create()生成的WPF专用Cursor对象。

static Cursor InternalCreateCursor(System.Drawing.Bitmap bitmap, int xHotSpot, int yHotSpot)
{
    var iconInfo = new NativeMethods.IconInfo
    {
        xHotspot = xHotSpot,
        yHotspot = yHotSpot,
        fIcon = false
    };

    NativeMethods.GetIconInfo(bitmap.GetHicon(), ref iconInfo);
    var cursorHandle = NativeMethods.CreateIconIndirect(ref iconInfo);
    
    // 转换为WPF Cursor对象
    return CursorInteropHelper.Create(cursorHandle);
}

第3步:封装UIElement转光标的高级方法

WPF的强大之处在于可以直接将任何UIElement渲染为图片。我们利用RenderTargetBitmap捕获控件外观,转为PNG流,再加载为Bitmap供底层API使用。

public static Cursor CreateCursor(UIElement element, int xHotSpot = 0, int yHotSpot = 0)
{
    // 1. 测量并排列元素,确保其计算了 DesiredSize
    element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
    element.Arrange(new Rect(new Point(), element.DesiredSize));

    // 2. 渲染为位图
    var renderTargetBitmap = new RenderTargetBitmap(
        (int)element.DesiredSize.Width, (int)element.DesiredSize.Height,
        96, 96, PixelFormats.Pbgra32);
    renderTargetBitmap.Render(element);

    // 3. 编码为PNG流
    var encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

    using (var memoryStream = new MemoryStream())
    {
        encoder.Save(memoryStream);
        // 4. 将内存流转为Drawing.Bitmap供API使用
        using (var bitmap = new System.Drawing.Bitmap(memoryStream))
        {
            return InternalCreateCursor(bitmap, xHotSpot, yHotSpot);
        }
    }
}

第4步:应用测试

可以直接传入任何复杂的WPF控件(如包含动画、样式的高级UserControl):

this.Cursor = CursorHelper.CreateCursor(new UserControl1());

完成判断与注意事项

完成判断:移动鼠标至目标区域,光标形状应变为自定义图形,且在指定坐标处产生点击事件响应(可通过模拟点击验证热点位置是否准确)。

重要提醒:

  1. 内存管理:虽然封装了SafeHandle,但在高频创建删除光标的场景下,仍需确保Bitmap和Stream被正确Dispose。
  2. 引用依赖:WPF方案强依赖System.Drawing.Common。若项目为纯Core且禁用System.Drawing,需自行实现DIB(设备无关位图)构建逻辑,或使用第三方库。
  3. 透明背景:绘制光标时,建议保留Alpha通道,部分格式可能不支持透明效果。

以上就是C#对桌面应用程序自定义鼠标光标的详细内容,更多关于C# WinForm WPF GUI开发技巧的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表