Pergunta

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

Foi útil?

Solução

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

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

  2. Crie um novo objeto 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 "ponto quente" indicando em que ponto da imagem o cursor está posicionado.

Se você optar por carregar a partir de um arquivo, observe que você precisa de um caminho absoluto do sistema de arquivos para usar o Cursor(string fileName) construtor.Lamely, um caminho relativo ou Pack URI 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 fluxo do arquivo e passá-lo para o Cursor(Stream cursorStream) construtor.Irritante, mas é verdade.

Por outro lado, especificar um cursor como caminho relativo ao carregá-lo usando um atributo XAML faz funcionar, um fato que você pode usar para carregar o cursor em um controle oculto e depois copiar a referência para usar em outro controle.Eu não tentei, mas deve funcionar.

Outras dicas

Como Peter mencionou acima, se você já possui um arquivo .cur, pode usá-lo como um recurso incorporado criando um elemento fictício na seção de recursos e, em seguida, referenciando o cursor do fictício quando precisar.

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

Adicione aos 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>

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

Existe uma maneira mais fácil do que gerenciar você mesmo a exibição do cursor ou usar o Visual Studio para construir vários cursores personalizados.

Se você tiver um FrameworkElement, poderá construir um Cursor a partir 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<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);
}

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

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

Seria usado assim:

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

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

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

Para usar um cursor personalizado em XAML, alterei ligeiramente o código fornecido por Ben McIntosh:

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

Para usar o cursor basta fazer referência ao recurso:

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

Uma maneira muito fácil é criar o cursor no Visual Studio como um arquivo .cur e adicioná-lo aos Recursos 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 cursor, combinei as soluções de Raio e 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);
    }

Eu sei que este tópico já existe há alguns anos, mas ontem eu queria carregar um arquivo de cursor personalizado dos recursos do projeto e tive problemas semelhantes.Procurei na internet uma solução e não encontrei o que precisava:para definir o this.Cursor para um cursor personalizado armazenado na minha pasta de recursos do meu projeto em tempo de execução.Tentei a solução xaml de Ben, mas não achei elegante o suficiente.PeterAllen declarou:

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

Eu tropecei em uma boa maneira de fazer isso e resolvi 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); 

Mais uma solução um tanto semelhante à de Ray, mas em vez de cópia lenta e complicada de pixels, usa alguns componentes 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 prefiro ter em uma classe 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 ponto de acesso, você pode até reduzi-lo (você também não precisa da estrutura ou do 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));
}

Você poderia tentar isso

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

Confira também BabySmash de Scott Hanselman (www.codeplex.com/babysmash).Ele usou um método mais de "força bruta" para ocultar o cursor do Windows e mostrar seu novo cursor em uma tela e, em seguida, mover o cursor para onde o cursor "real" estaria

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ê acabará com um vazamento de memória.O código a seguir (método de extensão para ícone) funciona perfeitamente para WPF.Ele 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.Ele não usa um controle de UI 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;
            }
        }
    }

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

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

  1. Novo arquivo de cursor
  2. Copie/cole a imagem
  3. Salve-o no arquivo .cur.

você pode fazer isso por código como

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

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

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top