Pregunta

Quiero usar una imagen o ícono como cursor personalizado en la aplicación WPF.¿Cuál es la mejor manera de hacerlo?

¿Fue útil?

Solución

Tienes dos opciones básicas:

  1. Cuando el cursor del mouse esté sobre su control, oculte el cursor del sistema configurando this.Cursor = Cursors.None; y dibuja tu propio cursor usando la técnica que quieras.Luego, actualice la posición y apariencia de su cursor respondiendo a los eventos del mouse.Aquí hay dos ejemplos:

  2. Cree un nuevo objeto Cursor cargando una imagen desde un archivo .cur o .ani.Puede crear y editar este tipo de archivos en Visual Studio.También hay algunas utilidades gratuitas disponibles para solucionarlos.Básicamente son imágenes (o imágenes animadas) que especifican un "punto caliente" que indica en qué punto de la imagen está colocado el cursor.

Si elige cargar desde un archivo, tenga en cuenta que necesita una ruta absoluta del sistema de archivos para usar el Cursor(string fileName) constructor.sin convicción, una ruta relativa o Pack URI no funcionará. Si necesita cargar el cursor desde una ruta relativa o desde un recurso empaquetado con su ensamblado, necesitará obtener una secuencia del archivo y pasarla al Cursor(Stream cursorStream) constructor.Molesto pero cierto.

Por otro lado, especificar un cursor como ruta relativa al cargarlo usando un atributo XAML hace funciona, un hecho que podría usar para cargar el cursor en un control oculto y luego copiar la referencia para usarla en otro control.No lo he probado, pero debería funcionar.

Otros consejos

Como Peter mencionó anteriormente, si ya tiene un archivo .cur, puede usarlo como un recurso incrustado creando un elemento ficticio en la sección de recursos y luego haciendo referencia al cursor del ficticio cuando lo necesite.

Por ejemplo, supongamos que desea mostrar cursores no estándar según la herramienta seleccionada.

Añadir a recursos:

<Window.Resources>
    <ResourceDictionary>
        <TextBlock x:Key="CursorGrab" Cursor="Resources/Cursors/grab.cur"/>
        <TextBlock x:Key="CursorMagnify" Cursor="Resources/Cursors/magnify.cur"/>
    </ResourceDictionary>
</Window.Resources>

Ejemplo de cursor incrustado al que se hace referencia en el código:

if (selectedTool == "Hand")
    myCanvas.Cursor = ((TextBlock)this.Resources["CursorGrab"]).Cursor;
else if (selectedTool == "Magnify")
    myCanvas.Cursor = ((TextBlock)this.Resources["CursorMagnify"]).Cursor;
else
    myCanvas.Cursor = Cursor.Arrow;

-Ben

Existe una manera más sencilla que administrar la visualización del cursor usted mismo o usar Visual Studio para construir muchos cursores personalizados.

Si tiene un FrameworkElement, puede construir un Cursor a partir de él usando el siguiente código:

public Cursor ConvertToCursor(FrameworkElement visual, Point hotSpot)
{
  int width = (int)visual.Width;
  int height = (int)visual.Height;

  // Render to a bitmap
  var bitmapSource = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
  bitmapSource.Render(visual);

  // Convert to System.Drawing.Bitmap
  var pixels = new int[width*height];
  bitmapSource.CopyPixels(pixels, width, 0);
  var bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
  for(int y=0; y<height; y++)
    for(int x=0; x<width; x++)
      bitmap.SetPixel(x, y, Color.FromArgb(pixels[y*width+x]));

  // Save to .ico format
  var stream = new MemoryStream();
  System.Drawing.Icon.FromHandle(resultBitmap.GetHicon()).Save(stream);

  // Convert saved file into .cur format
  stream.Seek(2, SeekOrigin.Begin);
  stream.WriteByte(2);
  stream.Seek(10, SeekOrigin.Begin);
  stream.WriteByte((byte)(int)(hotSpot.X * width));
  stream.WriteByte((byte)(int)(hotSpot.Y * height));
  stream.Seek(0, SeekOrigin.Begin);

  // Construct Cursor
  return new Cursor(stream);
}

Tenga en cuenta que el tamaño de su FrameworkElement debe ser un tamaño de cursor estándar (por ejemplo, 16x16 o 32x32), por ejemplo:

<Grid x:Name="customCursor" Width="32" Height="32">
  ...
</Grid>

Se usaría así:

someControl.Cursor = ConvertToCursor(customCursor, new Point(0.5, 0.5));

Obviamente su FrameworkElement podría ser un <Image> controla si tienes una imagen existente, o puedes dibujar lo que quieras usando las herramientas de dibujo integradas de WPF.

Tenga en cuenta que los detalles sobre el formato de archivo .cur se pueden encontrar en ICO (formato de archivo).

Para usar un cursor personalizado en XAML modifiqué ligeramente el código que Ben McIntosh proporcionó:

<Window.Resources>    
 <Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>

Para usar el cursor simplemente haga referencia al recurso:

<StackPanel Cursor="{StaticResource OpenHandCursor}" />

Una manera muy sencilla es crear el cursor dentro de Visual Studio como un archivo .cur y luego agregarlo a los recursos del proyecto.

Luego simplemente agregue el siguiente código cuando desee asignar el cursor:

myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));

En caso de que alguien esté buscando un UIElement como cursor, combiné las soluciones de Rayo y Arcturus:

    public Cursor ConvertToCursor(UIElement control, Point hotSpot)
    {
        // convert FrameworkElement to PNG stream
        var pngStream = new MemoryStream();
        control.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        Rect rect = new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height);
        RenderTargetBitmap rtb = new RenderTargetBitmap((int)control.DesiredSize.Width, (int)control.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);

        control.Arrange(rect);
        rtb.Render(control);

        PngBitmapEncoder png = new PngBitmapEncoder();
        png.Frames.Add(BitmapFrame.Create(rtb));
        png.Save(pngStream);

        // write cursor header info
        var cursorStream = new MemoryStream();
        cursorStream.Write(new byte[2] { 0x00, 0x00 }, 0, 2);                               // ICONDIR: Reserved. Must always be 0.
        cursorStream.Write(new byte[2] { 0x02, 0x00 }, 0, 2);                               // ICONDIR: Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid
        cursorStream.Write(new byte[2] { 0x01, 0x00 }, 0, 2);                               // ICONDIR: Specifies number of images in the file.
        cursorStream.Write(new byte[1] { (byte)control.DesiredSize.Width }, 0, 1);          // ICONDIRENTRY: Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels.
        cursorStream.Write(new byte[1] { (byte)control.DesiredSize.Height }, 0, 1);         // ICONDIRENTRY: Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels.
        cursorStream.Write(new byte[1] { 0x00 }, 0, 1);                                     // ICONDIRENTRY: Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette.
        cursorStream.Write(new byte[1] { 0x00 }, 0, 1);                                     // ICONDIRENTRY: Reserved. Should be 0.
        cursorStream.Write(new byte[2] { (byte)hotSpot.X, 0x00 }, 0, 2);                    // ICONDIRENTRY: Specifies the horizontal coordinates of the hotspot in number of pixels from the left.
        cursorStream.Write(new byte[2] { (byte)hotSpot.Y, 0x00 }, 0, 2);                    // ICONDIRENTRY: Specifies the vertical coordinates of the hotspot in number of pixels from the top.
        cursorStream.Write(new byte[4] {                                                    // ICONDIRENTRY: Specifies the size of the image's data in bytes
                                          (byte)((pngStream.Length & 0x000000FF)),
                                          (byte)((pngStream.Length & 0x0000FF00) >> 0x08),
                                          (byte)((pngStream.Length & 0x00FF0000) >> 0x10),
                                          (byte)((pngStream.Length & 0xFF000000) >> 0x18)
                                       }, 0, 4);
        cursorStream.Write(new byte[4] {                                                    // ICONDIRENTRY: Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file
                                          (byte)0x16,
                                          (byte)0x00,
                                          (byte)0x00,
                                          (byte)0x00,
                                       }, 0, 4);

        // copy PNG stream to cursor stream
        pngStream.Seek(0, SeekOrigin.Begin);
        pngStream.CopyTo(cursorStream);

        // return cursor stream
        cursorStream.Seek(0, SeekOrigin.Begin);
        return new Cursor(cursorStream);
    }

Sé que este tema ya tiene algunos años, pero ayer quise cargar un archivo de cursor personalizado desde los recursos del proyecto y me encontré con problemas similares.Busqué una solución en Internet y no encontré lo que necesitaba:para establecer el this.Cursor a un cursor personalizado almacenado en mi carpeta de recursos en mi proyecto en tiempo de ejecución.Probé la solución xaml de Ben, pero no la encontré lo suficientemente elegante.Peter Allen declaró:

Lamentablemente, una ruta relativa o un URI de paquete no funcionará.Si necesita cargar el cursor desde una ruta relativa o desde un recurso empaquetado con su ensamblado, necesitará obtener una secuencia del archivo y pasarla al constructor Cursor(Stream cursorStream).Molesto pero cierto.

Me topé con una buena manera de hacer esto y resuelve mi problema:

System.Windows.Resources.StreamResourceInfo info = Application.GetResourceStream(new Uri("/MainApp;component/Resources/HandDown.cur", UriKind.Relative));
this.Cursor = new System.Windows.Input.Cursor(info.Stream); 

Una solución más algo similar a la de Ray, pero en lugar de una copia de píxeles lenta y engorrosa, utiliza algunas funciones internas de Windows:

private struct IconInfo {
  public bool fIcon;
  public int xHotspot;
  public int yHotspot;
  public IntPtr hbmMask;
  public IntPtr hbmColor;
}

[DllImport("user32.dll")]
private static extern IntPtr CreateIconIndirect(ref IconInfo icon);

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

public Cursor ConvertToCursor(FrameworkElement cursor, Point HotSpot) {
  cursor.Arrange(new Rect(new Size(cursor.Width, cursor.Height)));
  var bitmap = new RenderTargetBitmap((int)cursor.Width, (int)cursor.Height, 96, 96, PixelFormats.Pbgra32);
  bitmap.Render(cursor);

  var info = new IconInfo();
  GetIconInfo(bitmap.ToBitmap().GetHicon(), ref info);
  info.fIcon = false;
  info.xHotspot = (byte)(HotSpot.X * cursor.Width);
  info.yHotspot = (byte)(HotSpot.Y * cursor.Height);

  return CursorInteropHelper.Create(new SafeFileHandle(CreateIconIndirect(ref info), true));
}

Hay un método de extensión en el medio que prefiero tener en una clase de extensión para tales casos:

using DW = System.Drawing;

public static DW.Bitmap ToBitmap(this BitmapSource bitmapSource) {
  var bitmap = new DW.Bitmap(bitmapSource.PixelWidth, bitmapSource.PixelHeight, DW.Imaging.PixelFormat.Format32bppPArgb);
  var data = bitmap.LockBits(new DW.Rectangle(DW.Point.Empty, bitmap.Size), DW.Imaging.ImageLockMode.WriteOnly, DW.Imaging.PixelFormat.Format32bppPArgb);
  bitmapSource.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
  bitmap.UnlockBits(data);
  return bitmap;
}

Con todo esto, es bastante simple y directo.

Y, si no necesita especificar su propio punto de acceso, puede incluso acortarlo (tampoco necesita la estructura ni los P/Invokes):

public Cursor ConvertToCursor(FrameworkElement cursor, Point HotSpot) {
  cursor.Arrange(new Rect(new Size(cursor.Width, cursor.Height)));
  var bitmap = new RenderTargetBitmap((int)cursor.Width, (int)cursor.Height, 96, 96, PixelFormats.Pbgra32);
  bitmap.Render(cursor);
  var icon = System.Drawing.Icon.FromHandle(bitmap.ToBitmap().GetHicon());
  return CursorInteropHelper.Create(new SafeFileHandle(icon.Handle, true));
}

Podrías probar esto

<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />

Consulte también BabySmash de Scott Hanselman (www.codeplex.com/babysmash).Usó un método más de "fuerza bruta" para ocultar el cursor de Windows y mostrar su nuevo cursor en un lienzo y luego mover el cursor a donde habría estado el cursor "real".

Leer más aquí:http://www.hanselman.com/blog/DeveloperDesigner.aspx

Asegúrese de eliminar cualquier recurso GDI (por ejemplo, bmp.GetHIcon).De lo contrario terminarás con una pérdida de memoria.El siguiente código (método de extensión para ícono) funciona perfectamente para WPF.Crea el cursor de flecha con un pequeño icono en la parte inferior derecha.

Observación:Este código utiliza un icono para crear el cursor.No utiliza un control de interfaz de usuario actual.

Matías

    public static Cursor CreateCursor(this Icon icon, bool includeCrossHair, System.Drawing.Color crossHairColor)
    {
        if (icon == null)
            return Cursors.Arrow;

        // create an empty image
        int width = icon.Width;
        int height = icon.Height;

        using (var cursor = new Bitmap(width * 2, height * 2))
        {
            // create a graphics context, so that we can draw our own cursor
            using (var gr = System.Drawing.Graphics.FromImage(cursor))
            {
                // a cursor is usually 32x32 pixel so we need our icon in the lower right part of it
                gr.DrawIcon(icon, new Rectangle(width, height, width, height));

                if (includeCrossHair)
                {
                    using (var pen = new System.Drawing.Pen(crossHairColor))
                    {
                        // draw the cross-hair
                        gr.DrawLine(pen, width - 3, height, width + 3, height);
                        gr.DrawLine(pen, width, height - 3, width, height + 3);
                    }
                }
            }

            try
            {
                using (var stream = new MemoryStream())
                {
                    // Save to .ico format
                    var ptr = cursor.GetHicon();
                    var tempIcon = Icon.FromHandle(ptr);
                    tempIcon.Save(stream);

                    int x = cursor.Width/2;
                    int y = cursor.Height/2;

                    #region Convert saved stream into .cur format

                    // set as .cur file format
                    stream.Seek(2, SeekOrigin.Begin);
                    stream.WriteByte(2);

                    // write the hotspot information
                    stream.Seek(10, SeekOrigin.Begin);
                    stream.WriteByte((byte)(width));
                    stream.Seek(12, SeekOrigin.Begin);
                    stream.WriteByte((byte)(height));

                    // reset to initial position
                    stream.Seek(0, SeekOrigin.Begin);

                    #endregion


                    DestroyIcon(tempIcon.Handle);  // destroy GDI resource

                    return new Cursor(stream);
                }
            }
            catch (Exception)
            {
                return Cursors.Arrow;
            }
        }
    }

    /// <summary>
    /// Destroys the icon.
    /// </summary>
    /// <param name="handle">The handle.</param>
    /// <returns></returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static Boolean DestroyIcon(IntPtr handle);

Si está utilizando Visual Studio, puede

  1. Nuevo archivo de cursor
  2. Copiar/Pegar la imagen
  3. Guárdelo en un archivo .cur.

puedes hacer esto por código como

this.Cursor = new Cursor(@"<your address of icon>");

Es posible que haya cambiado con Visual Studio 2017, pero pude hacer referencia a un archivo .cur como un recurso incrustado:

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top