3

I have an application which I'm interested in eventually porting to mono so I'm trying to avoid using p/invoke's to accomplish this task.

I would like to load a cursor dynamically, as in I have a Bitmap that is generated on the fly in the application. From what I can tell the safest way to do it without using p/invoke's is to create a .cur file which I can then load to a memory stream and use the Cursor(Stream) constructor. However I have no idea how to create a .cur file.

I found this article on the Microsoft Knowledge Base which sort of explains the format, but I'm not sure how it can be used without the interop calls. How To Create an Alpha Blended Cursor or Icon in Windows XP

Does anyone else have a managed solution I can use to accomplish this task?

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Josh Maag
  • 633
  • 8
  • 20
  • 2
    Since your code will be Windows specific, what's wrong with P/invoke? – David Heffernan Sep 30 '11 at 13:16
  • @DavidHeffernan Are you saying that mono will not let me load custom cursors in linux or OSX? – Josh Maag Sep 30 '11 at 13:19
  • I expect it will but the code will be completely different. You have to do this with platform specific code, or find a wrapper that someone else has produced with platform specific code. – David Heffernan Sep 30 '11 at 13:29
  • @DavidHeffernan So I cannot use this class in other operating systems even though it's part of the winforms section of .NET? http://msdn.microsoft.com/en-us/library/system.windows.forms.cursor%28v=VS.90%29.aspx – Josh Maag Sep 30 '11 at 13:39
  • System.Windows.Forms doesn't sound terribly portable. Does it exist in Mono? – David Heffernan Sep 30 '11 at 13:42
  • It does support Windows.Forms! :-) http://www.mono-project.com/Compatibility (Look in the .NET 2.0 section) It does not, however, support WPF, which is not an issue for me. – Josh Maag Sep 30 '11 at 13:46
  • And System.Windows.Forms doesn't support animated cursors. I also seem to recall that the WinForms support in Mono is patchy. – David Heffernan Sep 30 '11 at 13:49
  • Bitmap.GetHicon() gets you a handle that's suitable for the Cursor(IntPtr) constructor. But pinvoke is required to destroy the handle again, this isn't going to port well. http://stackoverflow.com/questions/3665196/how-can-i-use-an-image-list-to-draw-c-dragging-outside-of-my-own-windows/3667228# – Hans Passant Sep 30 '11 at 13:50
  • @HansPassant The answer that DevExpress gave in that question is very interesting. I wonder if that would work with my application if I hid the mouse. – Josh Maag Sep 30 '11 at 14:05

4 Answers4

1

I was reference the post: How can i replace cursor with bitmap in winform

You can create a Array of static cursor and use Timer to change it

to make dynamic mouse cursor effect!

Create static cursor from bitmap is so simply and without using 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;
    }
}
Community
  • 1
  • 1
IlPADlI
  • 1,943
  • 18
  • 22
1

Here's an example in VB.NET of creating a bitmap on the fly and turning it into a cursor. Not sure who this will help 7+ years on, but here goes:

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
edhubbell
  • 2,218
  • 1
  • 16
  • 17
0

The code below creates a stream filled with data in .cur format, which I succesfully tested in Ubuntu with Mono. However .NET without using Win32 API only supports black and white colors in custom cursor:

The Cursor class does not support animated cursors (.ani files) or cursors with colors other than black and white.

Therefore in Unix with Mono one is rewarded with a cursor which

  • displays black and white colors only
  • has no semi-transparent pixels, only fully opaque / fully transparent ones

¯\_(ツ)_/¯

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

Simple: YOU CAN NOT - the functionaltiy you ask for is not part of the .NET framework, so you need to go native.

If you application needds porting to mono, isloate this code in one class so you can turn if off like with a compiler switch - not hard.

TomTom
  • 61,059
  • 10
  • 88
  • 148
  • If I were to figure out the format of the .cur file, couldn't I then create that format in .NET? – Josh Maag Sep 30 '11 at 13:21
  • @liquid What would Linux or Mac do .cur file? – David Heffernan Sep 30 '11 at 13:34
  • @DavidHeffernan I was assuming I could load it into my .NET application to display my custom cursor. Using the Cursor class provided in the framework. I won't actually be giving out the .cur file to the user, I'm just assuming that's the best way to load a dynamically created one. – Josh Maag Sep 30 '11 at 13:37