我该如何在.NET中创建动态鼠标指针而不使用Interop?

3

我有一个应用程序,最终希望将其移植到mono上,因此我正在尝试避免使用p/invoke来完成此任务。

我想动态加载光标,也就是说,我在应用程序中生成了一个位图。据我所知,在不使用p/invoke的情况下最安全的方法是创建一个.cur文件,然后将其加载到内存流中并使用Cursor(Stream)构造函数。但是我不知道如何创建.cur文件。

我在Microsoft Knowledge Base上找到了这篇文章,它有点解释了格式,但我不确定如何在不使用Interop调用的情况下使用它。 如何在Windows XP中创建Alpha混合光标或图标

还有其他人有可以用来完成此任务的托管解决方案吗?


2
既然你的代码将是Windows特定的,那么使用P/invoke有什么问题呢? - David Heffernan
@DavidHeffernan,您是在说Mono不允许我在Linux或OSX中加载自定义光标吗? - Josh Maag
@DavidHeffernan 所以即使它是 .NET 的 winforms 部分的一部分,我也不能在其他操作系统中使用这个类吗?http://msdn.microsoft.com/en-us/library/system.windows.forms.cursor%28v=VS.90%29.aspx - Josh Maag
而且System.Windows.Forms不支持动画光标。我还记得Mono中的WinForms支持有所欠缺。 - David Heffernan
Bitmap.GetHicon() 获取一个句柄,适用于 Cursor(IntPtr) 构造函数。但是 pinvoke 是必需的,以便再次销毁句柄,否则这不会很好地移植。http://stackoverflow.com/questions/3665196/how-can-i-use-an-image-list-to-draw-c-dragging-outside-of-my-own-windows/3667228# - Hans Passant
显示剩余4条评论
4个回答

1
我在参考这篇文章:如何在winform中用位图替换光标 你可以创建一个静态光标的数组,并使用计时器来更改它,以实现动态鼠标光标效果!
从位图创建静态光标非常简单,而且不需要使用Interop:
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Icon icon = this.Icon;
        Bitmap bmp = icon.ToBitmap();
        Cursor cur = new Cursor(bmp.GetHicon());

        this.Cursor = cur;
    }
}

1

以下是VB.NET中创建位图并将其转换为光标的示例。不确定这对于7年后的任何人有帮助,但是请看下面:

Private Function GetCircleCursor(iDiameter As Integer) As Cursor
Dim oBitmap As Bitmap = New Bitmap(Convert.ToInt32(iDiameter), Convert.ToInt32(iDiameter), System.Drawing.Imaging.PixelFormat.Format32bppArgb)

    Using g As System.Drawing.Graphics = Graphics.FromImage(oBitmap)
        g.Clear(Color.Transparent)

        g.DrawEllipse(New System.Drawing.Pen(Color.White, 3), New Rectangle(0, 0, iDiameter, iDiameter))
        g.DrawEllipse(New System.Drawing.Pen(Color.Black, 1), New Rectangle(0, 0, iDiameter, iDiameter))
    End Using

    Return New Cursor(oBitmap.GetHicon)
End Function

0
下面的代码创建了一个以 .cur 格式填充数据的流,我已经在 Ubuntu 上使用 Mono 成功测试过。然而,在不使用 Win32 API 的情况下,.NET 仅支持黑白颜色 自定义光标:

Cursor 类不支持动画光标(.ani 文件)或颜色为黑色和白色以外的光标。

因此,在 Unix 上使用 Mono,你将得到一个光标,它
  • 仅显示黑白颜色
  • 没有半透明像素,只有完全不透明/完全透明的像素

¯\_(ツ)_/¯

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace StackoverflowExample
{
    public static class CursorHelper
    {
        public static Cursor CreateCursor(Bitmap bmp, Point hotSpot)
        {
            // https://en.wikipedia.org/wiki/ICO_(file_format)
            var bmpData = bmp.LockBits(new Rectangle(default, bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

            try
            {
                int numBytes = bmpData.Stride * bmpData.Height;
                var bgraValues = new byte[numBytes];
                Marshal.Copy(bmpData.Scan0, bgraValues, 0, numBytes);

                int max = Math.Max(bmp.Width, bmp.Height);

                if (max > 256)
                    throw new NotSupportedException();

                byte iconSizeByte = _sizes.FirstOrDefault(s => s >= max); // 0 means 256
                int iconSizeI = iconSizeByte == 0 ? 256 : iconSizeByte;
                const int bytesPerPixel = 4;
                const int bytesPerPixelSource = 4;
                byte[] emptyPixel = new byte[bytesPerPixel];

                using (var stream = new MemoryStream())
                using (var writer = new BinaryWriter(stream))
                {
                    writer.Write((ushort)0); // idReserved
                    writer.Write((ushort)2); // idType, 1 = .ico 2 = .cur
                    writer.Write((ushort)1); // idCount

                    writer.Write(iconSizeByte);
                    writer.Write(iconSizeByte);
                    writer.Write((byte)0); // colorCount
                    writer.Write((byte)0); // reserved
                    writer.Write((ushort)hotSpot.X);
                    writer.Write((ushort)hotSpot.Y);

                    var pixelsCount = iconSizeI * iconSizeI;
                    var xorLength = pixelsCount * bytesPerPixel;
                    var andLength = pixelsCount / 8 * 2;

                    writer.Write((uint)(40 + xorLength + andLength)); // sizeInBytes
                    writer.Write((uint)stream.Position + sizeof(uint)); // fileOffset = 22 = 0x16

                    writer.Write(40u); // cursorInfoHeader.biSize
                    writer.Write((int)iconSizeI); // cursorInfoHeader.biWidth
                    writer.Write((int)iconSizeI * 2); // cursorInfoHeader.biHeight
                    writer.Write((ushort)1); // cursorInfoHeader.biPlanes
                    writer.Write((ushort)(8 * bytesPerPixel)); // cursorInfoHeader.biBitCount
                    writer.Write(0u); // cursorInfoHeader.biCompression
                    writer.Write(0u); // cursorInfoHeader.biSizeImage
                    writer.Write(0); // cursorInfoHeader.biXPelsPerMeter;
                    writer.Write(0); // cursorInfoHeader.biYPelsPerMeter;
                    writer.Write(0u); // cursorInfoHeader.biClrUsed = binaryReader2.ReadUInt32();
                    writer.Write(0u); // cursorInfoHeader.biClrImportant = binaryReader2.ReadUInt32();

                    using (var andMask = new MemoryStream(andLength))
                    {
                        byte def = 255;

                        for (int j = 0; j < iconSizeI; j++)
                        {
                            int y = iconSizeI - 1 - j;
                            byte curByte = def;

                            for (int i = 0; i < iconSizeI; i++)
                            {
                                var bitIndex = 7 - i % 8;

                                if (i < bmp.Width && y < bmp.Height)
                                {
                                    var p = y * bmpData.Stride + i * bytesPerPixelSource;
                                    stream.Write(bgraValues, p, bytesPerPixel);

                                    if (bgraValues[p + 3] > 0)
                                        curByte = (byte)(curByte & ~(1 << bitIndex));
                                }
                                else
                                    stream.Write(emptyPixel, 0, emptyPixel.Length);

                                if (bitIndex == 0)
                                {
                                    andMask.WriteByte(curByte);
                                    curByte = def;
                                }
                            }
                        }

                        for (int j = 0; j < iconSizeI; j++)
                            for (int b = 0; b < iconSizeI / 8; b++)
                                andMask.WriteByte(def);

                        andMask.Seek(0, SeekOrigin.Begin);
                        andMask.CopyTo(stream);
                    }

                    stream.Seek(0, SeekOrigin.Begin);

                    // debug
                    // File.WriteAllBytes("/home/kolia/Documents/stream", stream.ToArray());
                    // stream.Seek(0, SeekOrigin.Begin);

                    var cursor = new Cursor(stream);
                    return cursor;
                }
            }
            finally
            {
                bmp.UnlockBits(bmpData);
            }
        }

        private static readonly byte[] _sizes = { 16, 32, 64, 128 };
    }
}

-1
简单来说:你不能这样做——你所要求的功能不是.NET框架的一部分,因此你需要使用本地代码。
如果你的应用程序需要移植到Mono,那么将这段代码隔离在一个类中,这样你就可以像使用编译器开关一样关闭它——这并不难。

如果我能弄清楚 .cur 文件的格式,那么我就可以在 .NET 中创建该格式,对吗? - Josh Maag
@liquid Linux或Mac会如何处理.cur文件? - David Heffernan
@DavidHeffernan 我假设我可以将它加载到我的 .NET 应用程序中以显示我的自定义光标,使用框架提供的 Cursor 类。我实际上不会向用户提供 .cur 文件,我只是假设这是加载动态创建的文件的最佳方法。 - Josh Maag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接