Cursor personalizado no WPF?

Eu quero usar uma imagem ou ícone como um cursor personalizado no aplicativo WPF. Qual é a melhor maneira de fazer isso?

Você tem duas opções básicas:

  1. Quando o cursor do mouse estiver sobre o seu controle, oculte o cursor do sistema, definindo this.Cursor = Cursors.None; e desenhe seu próprio cursor usando qualquer técnica que desejar. Em seguida, atualize a posição e a aparência do cursor respondendo a events do mouse. Aqui estão dois exemplos:

  2. Crie um novo object Cursor carregando uma imagem de um arquivo .cur ou .ani. Você pode criar e editar esses tipos de arquivos no Visual Studio. Existem também alguns utilitários gratuitos disponíveis para lidar com eles. Basicamente são imagens (ou imagens animadas) que especificam um “hot spot” indicando em que ponto da imagem o cursor está posicionado.

Se você optar por carregar de um arquivo, observe que é necessário um caminho absoluto do sistema de arquivos para usar o construtor Cursor(string fileName) . Lamentavelmente, um caminho relativo ou URI do pacote não funcionará. Se você precisar carregar o cursor de um caminho relativo ou de um recurso compactado com seu assembly, você precisará obter um stream do arquivo e passá-lo para o construtor Cursor(Stream cursorStream) . Irritante mas verdadeiro.

Por outro lado, especificar um cursor como um caminho relativo ao carregá-lo usando um atributo XAML funciona, um fato que você poderia usar para obter o cursor carregado em um controle oculto e copie a referência para usar em outro controle. Eu não tentei, mas deveria funcionar.

Como Peter mencionado acima, se você já tiver um arquivo .cur, poderá usá-lo como um recurso incorporado, criando um elemento fictício na seção de resources e fazendo referência ao cursor do manequim quando precisar dele.

Por exemplo, digamos que você deseje exibir cursores não padrão dependendo da ferramenta selecionada.

Adicione aos resources:

       

Exemplo de cursor incorporado referenciado no 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

Há uma maneira mais fácil do que gerenciar o cursor exibir você mesmo ou usar o Visual Studio para construir muitos cursores personalizados.

Se você tiver um FrameworkElement, poderá construir um Cursor dele usando o seguinte 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 

Observe que o tamanho do seu FrameworkElement deve ser um tamanho de cursor padrão (por exemplo, 16x16 ou 32x32), por exemplo:

  ...  

Seria usado assim:

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

Obviamente, o seu FrameworkElement poderia ser um controle de se você tivesse uma imagem existente, ou você pode desenhar o que quiser usando as ferramentas de desenho internas do WPF.

Observe que detalhes no formato de arquivo .cur podem ser encontrados em ICO (formato de arquivo) .

Para usar um cursor personalizado no XAML, alterei o código que o Ben McIntosh forneceu ligeiramente:

  Resources/openhand.cur  

Para usar o cursor apenas faça referência ao recurso:

  

Eu sei que este tópico é de alguns anos agora, mas ontem eu queria carregar um arquivo de cursor personalizado a partir dos resources do projeto e correu para problemas semelhantes. Eu procurei na internet por uma solução e não encontrei o que eu precisava: para definir o this.Cursor para um cursor personalizado armazenado na minha pasta de resources no meu projeto em tempo de execução. Eu tentei a solução xaml de Ben, mas não achei elegante o suficiente. PeterAllen declarou:

Lamentavelmente, um caminho relativo ou URI do pacote não funcionará. Se você precisar carregar o cursor de um caminho relativo ou de um recurso compactado com seu assembly, você precisará obter um stream do arquivo e passá-lo para o construtor Cursor (Stream cursorStream). Irritante mas verdadeiro.

Eu tropecei em uma maneira legal de fazer isso e resolve o meu 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); 

Uma maneira muito fácil é criar o cursor no Visual Studio como um arquivo .cur e adicioná-lo aos resources do projeto.

Em seguida, basta adicionar o seguinte código quando quiser atribuir o cursor:

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

Caso alguém esteja procurando um UIElement como um cursor, eu combinei as soluções do Ray e do 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); } 

Mais uma solução parecida com a de Ray, mas em vez de cópia de pixel lenta e incômoda, isso usa alguns resources internos do 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)); } 

Existe um método de extensão no meio que eu prefiro ter em uma class de extensão para tais 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; } 

Com tudo isso, é bastante simples e direto.

E, se acontecer de você não precisar especificar seu próprio hotspot, você pode até cortar este menor (você não precisa do struct ou do P / Invokes, também):

 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)); } 

Você poderia tentar isso

  

Confira também o BabySmash de Scott Hanselman (www.codeplex.com/babysmash). Ele usou um método mais “força bruta” de esconder o cursor do Windows e mostrar seu novo cursor em uma canvas e, em seguida, mover o cursor para o cursor “real” teria sido

Leia mais aqui: http://www.hanselman.com/blog/DeveloperDesigner.aspx

Certifique-se de que qualquer recurso GDI (por exemplo, bmp.GetHIcon) seja descartado. Caso contrário, você acaba com um memory leaks. O código a seguir (método de extensão para ícone) funciona perfeitamente para o WPF. Cria o cursor de seta com um pequeno ícone no canto inferior direito.

Observação: este código usa um ícone para criar o cursor. Não usa um controle de interface do usuário atual.

Matias

  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); 

Se você estiver usando o visual studio, você pode

  1. Novo um arquivo de cursor
  2. Copiar / colar a imagem
  3. Salve-o no arquivo .cur.

Pode ter mudado com o Visual Studio 2017, mas consegui referenciar um arquivo .cur como um recurso incorporado:

  

Aqui está um utilitário gratuito rico em resources que permite criar um arquivo cur a partir de qualquer imagem: http://www.rw-designer.com/cursor-maker

Chama-se RealWorld Cursor Editor.

E aqui está um link sobre como incorporar um cursor em um projeto:

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

você pode fazer isso por código como

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