Cursor personalizado no WPF?
Pergunta
Quero usar uma imagem ou ícone como cursor personalizado no aplicativo WPF.Qual é a melhor maneira de fazer isso?
Solução
Você tem duas opções básicas:
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: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
- Novo arquivo de cursor
- Copie/cole a imagem
- 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" />