¿Cursor personalizado en WPF?

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

Tienes dos opciones básicas:

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

  2. Cree un nuevo objeto Cursor cargando una imagen de un archivo .cur o .ani. Puede crear y editar este tipo de archivos en Visual Studio. También hay algunos utilites gratuitos flotando para tratar con ellos. Básicamente son imágenes (o imágenes animadas) que especifican un “punto caliente” que indica en qué punto de la imagen está posicionado el cursor.

Si elige cargar desde un archivo, tenga en cuenta que necesita una ruta absoluta del sistema de archivos para usar el constructor Cursor(string fileName) . Lamely, una ruta relativa o 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

Por otro lado, la especificación de un cursor como una ruta relativa cuando se carga con un atributo XAML funciona, un hecho que podría usar para cargar el cursor en un control oculto y luego copiar la referencia para usar en otro control. No lo he intentado, pero debería funcionar.

Al igual que Peter mencionado 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 maniquí cuando lo necesite.

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

Agregar a los recursos:

       

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

Hay una forma más fácil 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 utilizando 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 

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:

  ...  

Se usaría así:

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

Obviamente, su FrameworkElement podría ser un control de si tiene una imagen existente, o puede dibujar lo que quiera utilizando las herramientas de dibujo integradas de WPF.

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

Para usar un cursor personalizado en XAML alteré el código proporcionado por Ben McIntosh ligeramente:

  Resources/openhand.cur  

Para usar el cursor simplemente haga referencia al recurso:

  

Sé que este tema tiene algunos años ahora, pero ayer quería cargar un archivo de cursor personalizado de los recursos del proyecto y encontré problemas similares. Busqué una solución en Internet y no encontré lo que necesitaba: configurar esto. 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 me pareció lo suficientemente elegante. Peter Allen dijo:

Lamely, una ruta relativa o 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 tropecé 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 forma muy sencilla es crear el cursor en Visual Studio como un archivo .cur y luego agregarlo a los recursos de los proyectos.

Luego solo agregue el siguiente código cuando quiera 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 Ray 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); } 

Una solución más similar a la de Ray, pero en lugar de una copia de píxeles lenta e incómoda, utiliza algunos componentes internos 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 necesitas especificar tu propio punto de acceso, incluso puedes cortarlo más corto (tampoco necesitas la estructura o el P / Invoca):

 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

  

También echa un vistazo a BabySmash de Scott Hanselman (www.codeplex.com/babysmash). Utilizó un método más “fuerza bruta” para ocultar el cursor de Windows y mostrar su nuevo cursor sobre un canvas y luego mover el cursor hacia donde se encontraba el cursor “real”.

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

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

Observación: Este código usa un ícono para crear el cursor. No usa un control de UI actual.

Matthias

  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; } } } ///  /// Destroys the icon. ///  /// The handle. ///  [DllImport("user32.dll", CharSet = CharSet.Auto)] public extern static Boolean DestroyIcon(IntPtr handle); 

Si está usando Visual Studio, puede

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

Puede haber cambiado con Visual Studio 2017, pero pude hacer referencia a un archivo .cur como un recurso incrustado:

  

Aquí hay una utilidad gratuita y rica en funciones que le permite crear un archivo cur de cualquier imagen: http://www.rw-designer.com/cursor-maker

Se llama RealWorld Cursor Editor.

Y aquí hay un enlace sobre cómo incrustar un cursor en un proyecto:

http://wpf.2000things.com/tag/cursor/

puedes hacer esto por código como

 this.Cursor = new Cursor(@"");