在WPF中自定义光标?

我想在WPF应用程序中使用图像或图标作为自定义光标。 什么是最好的办法呢?

你有两个基本的select:

  1. 当鼠标光标在你的控制上时,通过设置this.Cursor = Cursors.None;隐藏系统光标。游标this.Cursor = Cursors.None; 并使用任何你喜欢的技术绘制自己的光标。 然后,通过响应鼠标事件来更新光标的位置和外观。 这里有两个例子:

  2. 通过从.cur或.ani文件加载图像来创build一个新的Cursor对象。 您可以在Visual Studio中创build和编辑这些types的文件。 还有一些免费的应用程序在处理它们。 基本上他们是图像(或animation图像),它们指定了一个“热点”,指示图像中光标所在的位置。

如果您select从文件加载,请注意您需要绝对文件系统path来使用Cursor(string fileName)构造函数。 跛脚, 相对path或Pack URI将不起作用。 如果您需要从相对path或从您的程序集打包的资源中加载游标,则需要从文件中获取stream并将其传递给Cursor(Stream cursorStream)构造函数。 令人讨厌但真实。

另一方面,在使用XAML属性加载游标时,将游标指定为相对path行不通的,事实上,您可以使用它将游标加载到隐藏的控件上,然后复制该引用以在另一个控件上使用。 我没有尝试过,但它应该工作。

像上面提到的Peter一样,如果你已经有了一个.cur文件,你可以通过在资源部分创build一个虚拟元素,然后在你需要的时候引用虚拟的光标,把它用作embedded资源。

例如,假设你想显示非标准的光标,取决于所select的工具。

添加到资源:

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

embedded式游标在代码中引用的示例:

 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

比自己pipe理游标显示还是使用Visual Studio构build大量自定义游标更简单。

如果你有一个FrameworkElement,你可以使用下面的代码构造一个Cursor:

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

请注意,您的FrameworkElement的大小必须是标准游标大小(例如16×16或32×32),例如:

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

它会像这样使用:

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

显然你的FrameworkElement可以是一个<Image>控件,如果你有一个现有的图像,或者你可以使用WPF的内置绘图工具绘制任何你喜欢的东西。

请注意,.cur文件格式的详细信息可以在ICO(文件格式)中find 。

我知道这个主题现在已经有几年了,但是昨天我想从项目资源中加载一个自定义的光标文件,并遇到类似的问题。 我search了互联网的解决scheme,并没有find我所需要的:设置this.Cursor到我的资源文件夹中存储在我的项目在运行时的自定义光标。 我已经尝试了Ben的xaml解决scheme,但没有find足够的优雅。 彼得·艾伦说:

跛脚,相对path或Pack URI将不起作用。 如果您需要从相对path或从您的程序集打包的资源中加载游标,则需要从文件中获取stream并将其传递给Cursor(Stream cursorStream)构造函数。 令人讨厌但真实。

我偶然发现了一个很好的方法来解决我的问题:

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

一个非常简单的方法是在Visual Studio中创build光标作为.cur文件,然后将其添加到项目资源。

然后,当你想分配光标时只需添加下面的代码:

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

如果有人正在寻找UIElement本身作为游标,我结合了Ray和Arcturus的解决scheme:

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

在XAML中使用自定义光标我修改了Ben McIntosh提供的代码:

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

要使用游标只需引用资源:

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

还有一种解决scheme有点类似于Ray的解决scheme,而不是缓慢和繁琐的像素复制,它使用了一些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)); } 

中间有一个扩展方法,我更喜欢在扩展类中使用这种情况:

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

所有这一切,都是相当简单直接的。

而且,如果你碰巧不需要指定你自己的热点,你甚至可以把它缩短(你不需要结构体或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)); } 

你可以试试这个

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

如果你正在使用visual studio,你可以

  1. 新build一个游标文件
  2. 复制/粘贴图像
  3. 将其保存到.cur文件。

还可以看看Scott Hanselman的BabySmash(www.codeplex.com/babysmash)。 他用一种更“暴力”的方法隐藏窗口光标,并在canvas上显示新的光标,然后将光标移动到“真实”光标将会

阅读更多: http : //www.hanselman.com/blog/DeveloperDesigner.aspx

确保任何GDI资源(例如bmp.GetHIcon)被处置。 否则,最终会发生内存泄漏。 下面的代码(图标的扩展方法)完美适用于WPF。 它会在右下方创build一个带有小图标的箭头光标。

备注:此代码使用图标来创build游标。 它不使用当前的UI控件。

马蒂亚斯

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

这是非常好的,但我遇到了黑色的图像。

这一个复制所见即所得。

请参阅将任何控件转换为图像(WPF)

这是一个function丰富,免费的实用程序,允许您从任何图像创build一个cur文件: http : //www.rw-designer.com/cursor-maker

它被称为RealWorld光标编辑器。

下面是如何在项目中embedded游标的链接:

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

你可以通过代码来做到这一点

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

它可能已经改变了Visual Studio 2017,但我能够引用.cur文件作为embedded式资源:

 <Setter Property="Cursor" Value="/assembly-name;component/location-name/curser-name.cur" />