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

有帮助吗?

解决方案

您有两个基本选择:

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

  2. 通过从 .cur 或 .ani 文件加载图像来创建新的 Cursor 对象。您可以在 Visual Studio 中创建和编辑此类文件。还有一些免费的实用程序可以用来处理它们。基本上,它们是指定“热点”的图像(或动画图像),指示光标位于图像中的哪个点。

如果您选择从文件加载,请注意您需要绝对文件系统路径才能使用 Cursor(string fileName) 构造函数。跛脚, 相对路径或 Pack URI 将不起作用。 如果您需要从相对路径或从程序集打包的资源加载光标,则需要从文件中获取流并将其传递到 Cursor(Stream cursorStream) 构造函数。很烦人,但却是事实。

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

其他提示

就像上面提到的 Peter 一样,如果您已经有一个 .cur 文件,您可以通过在资源部分中创建一个虚拟元素来将其用作嵌入式资源,然后在需要时引用虚拟的光标。

例如,假设您想根据所选工具显示非标准光标。

添加到资源:

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

代码中引用的嵌入光标示例:

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;

-本

有一种比自己管理光标显示或使用 Visual Studio 构建大量自定义光标更简单的方法。

如果您有 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 的大小必须是标准光标大小(例如 16x16 或 32x32),例如:

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

它会像这样使用:

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

显然你的 FrameworkElement 可能是 <Image> 控制是否有现有图像,或者可以使用 WPF 的内置绘图工具绘制任何您喜欢的内容。

请注意,有关 .cur 文件格式的详细信息可以在以下位置找到: ICO(文件格式).

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

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

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

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

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

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

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

如果有人正在寻找 UIElement 本身作为光标,我结合了以下解决方案 射线大角星:

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

我知道这个主题已经有几年了,但昨天我想从项目资源加载自定义光标文件并遇到类似的问题。我在网上搜索了解决方案,但没有找到我需要的:设置 this.Cursor 到运行时存储在我的项目的资源文件夹中的自定义光标。我尝试过 Ben 的 xaml 解决方案,但发现它不够优雅。彼得·艾伦说道:

蹩脚的是,相对路径或 Pack URI 不起作用。如果需要从相对路径或从程序集打包的资源加载游标,则需要从文件中获取流并将其传递给 Cursor(StreamcursorStream) 构造函数。很烦人,但却是事实。

我偶然发现了一个很好的方法来做到这一点并解决了我的问题:

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

另一种解决方案有点类似于 Ray 的解决方案,但它使用了一些 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"" />

另请查看 Scott Hanselman 的 BabySmash (www.codeplex.com/babysmash)。他使用了一种更“暴力”的方法来隐藏窗口光标并在画布上显示他的新光标,然后将光标移动到“真实”光标所在的位置

在这里阅读更多内容:http://www.hanselman.com/blog/DeveloperDesigner.aspx

确保任何 GDI 资源(例如 bmp.GetHIcon)均已释放。否则你最终会出现内存泄漏。以下代码(图标的扩展方法)非常适合 WPF。它会创建右下角带有小图标的箭头光标。

评论:此代码使用图标来创建光标。它不使用当前的 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);

如果您使用的是 Visual Studio,您可以

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

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

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

它可能在 Visual Studio 2017 中发生了变化,但我能够引用 .cur 文件作为嵌入资源:

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top