WPF中的自定义光标?

Ala*_* Le 47 .net wpf image cursor

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

Pet*_*ebb 33

您有两个基本选项:

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

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

如果选择从文件加载,请注意您需要绝对文件系统路径才能使用Cursor(string fileName)构造函数.Lamely,相对路径或包URI不起作用.如果需要从相对路径或从程序集中打包的资源加载游标,则需要从文件中获取流并将其传递给Cursor(Stream cursorStream)构造函数.恼人但真实.

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

  • 不幸的是,第一个例子在未经授权的情况下不再有效. (8认同)
  • 另请注意,您可以从任何 WPF 内容动态构建光标。有关如何完成此操作的示例,请参阅http://stackoverflow.com/questions/2835502/rotating-cursor-according-to-rotated-textbox/2836904#2836904。 (2认同)

Ben*_*osh 31

与上面提到的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>
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

-ben

  • 没理由; FrameworkElement将是更好的选择.谢谢! (4认同)
  • 您是否有任何理由使用TextBlock在FrameworkElement上缓存Cursor引用,其中首先定义Cursor属性? (3认同)

Ray*_*rns 16

有一种比自己管理光标显示或使用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);
}
Run Code Online (Sandbox Code Playgroud)

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

<Grid x:Name="customCursor" Width="32" Height="32">
  ...
</Grid>
Run Code Online (Sandbox Code Playgroud)

它会像这样使用:

someControl.Cursor = ConvertToCursor(customCursor, new Point(0.5, 0.5));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 嘿,我尝试使用此代码段来定义带有xaml的自定义光标.不幸的是,它只显示任何内容,而不是我定义的`<Image />`元素.调试代码我意识到`var pixels`-array在`CopyPixels()`-method运行之后每个像素只包含0.我的`copyPixels()` - 方法的`stride`参数出错了,所以我根据我发现的其他一些片段改了一下代码:`int stride = width*((bitmapSource.Format.BitsPerPixel + 7)/ 8);`除了代码看起来像上面一样.`visual`是:`<Image Height ="32"Width ="32"/>` (3认同)

kkC*_*smo 11

为了在XAML中使用自定义游标,我改变了Ben McIntosh提供的代码:

<Window.Resources>    
 <Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>
Run Code Online (Sandbox Code Playgroud)

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

<StackPanel Cursor="{StaticResource OpenHandCursor}" />
Run Code Online (Sandbox Code Playgroud)


小智 10

如果有人正在寻找UIElement本身作为游标,我结合了RayArcturus的解决方案:

    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);
    }
Run Code Online (Sandbox Code Playgroud)


Gre*_*reg 9

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

然后,只需在分配光标时添加以下代码:

myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));
Run Code Online (Sandbox Code Playgroud)


小智 8

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

Lamely,相对路径或包URI不起作用.如果需要从相对路径或从程序集中打包的资源加载游标,则需要从文件中获取流并将其传递给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); 
Run Code Online (Sandbox Code Playgroud)


Gáb*_*bor 7

还有一个类似于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));
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

有了这一切,它相当简单明了.

而且,如果你不需要指定自己的热点,你甚至可以缩短它(你也不需要结构或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));
}
Run Code Online (Sandbox Code Playgroud)

  • 这个效果很好(从我想要的任何 WPF 视觉对象创建一个 Cursor 真是太棒了),但是,每当关联的对象被销毁时,我都会在此方法创建的 Cursor 的 dtor 中收到一个 SEH 异常。唯一不能得到它的方法是创建游标的单例并在任何地方重用它。您知道的任何原因都会导致 SEH 异常吗?我可以一整天都在猜测,但似乎用于为光标创建图像的对象被处理掉了,而 Cursor 类会炸毁它的 b/c。 (2认同)