.net WPF中的自定义光标?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/46805/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Custom cursor in WPF?
提问by Alan Le
I want to use an image or icon as a custom cursor in WPF app. What's the best way to do it?
我想在 WPF 应用程序中使用图像或图标作为自定义光标。最好的方法是什么?
采纳答案by PeterAllenWebb
You have two basic options:
您有两个基本选项:
When the mouse cursor is over your control, hide the system cursor by setting
this.Cursor = Cursors.None;and draw your own cursor using whatever technique you like. Then, update the position and appearance of your cursor by responding to mouse events. Here are two examples:- http://www.xamlog.com/2006/07/17/creating-a-custom-cursor/
http://www.hanselman.com/blog/DeveloperDesigner.aspx
Additional examples can be found here:- Setting the Cursor to Render Some Text While Dragging
- Getting fancy and using the Visual we are dragging for feedback [instead of a cursor]
- How can I drag and drop items between data bound ItemsControls?
Create a new Cursor object by loading an image from a .cur or .ani file. You can create and edit these kinds of files in Visual Studio. There are also some free utilites floating around for dealing with them. Basically they're images (or animated images) which specify a "hot spot" indicating what point in the image the cursor is positioned at.
当鼠标光标位于您的控件上时,通过
this.Cursor = Cursors.None;使用您喜欢的任何技术设置和绘制自己的光标来隐藏系统光标。然后,通过响应鼠标事件来更新光标的位置和外观。这里有两个例子:通过从 .cur 或 .ani 文件加载图像来创建新的 Cursor 对象。您可以在 Visual Studio 中创建和编辑这些类型的文件。还有一些免费的实用程序可以用来处理它们。基本上它们是图像(或动画图像),它们指定一个“热点”,指示光标位于图像中的哪个点。
If you choose to load from a file, note that you need an absolute file-system path to use the Cursor(string fileName)constructor. Lamely, a relative path or Pack URI will not work.If you need to load the cursor from a relative path or from a resource packed with your assembly, you will need to get a stream from the file and pass it in to the Cursor(Stream cursorStream)constructor. Annoying but true.
如果您选择从文件加载,请注意您需要一个绝对文件系统路径才能使用Cursor(string fileName)构造函数。遗憾的是,相对路径或 Pack URI 将不起作用。如果需要从相对路径或与程序集打包的资源加载游标,则需要从文件中获取流并将其传递给Cursor(Stream cursorStream)构造函数。烦人但真实。
On the other hand, specifying a cursor as a relative path when loading it using a XAML attribute doeswork, a fact you could use to get your cursor loaded onto a hidden control and then copy the reference to use on another control. I haven't tried it, but it should work.
另一方面,在使用 XAML 属性加载游标时将游标指定为相对路径确实有效,您可以使用这一事实将游标加载到隐藏控件上,然后复制引用以在另一个控件上使用。我没试过,但应该可以。
回答by Ben McIntosh
Like Peter mentioned above, if you already have a .cur file, you can use it as an embedded resource by creating a dummy element in the resource section, and then referencing the dummy's cursor when you need it.
像上面提到的 Peter 一样,如果您已经有一个 .cur 文件,您可以通过在资源部分创建一个虚拟元素来将其用作嵌入资源,然后在需要时引用虚拟元素的光标。
For example, say you wanted to display non-standard cursors depending on the selected tool.
例如,假设您想根据所选工具显示非标准光标。
Add to resources:
添加到资源:
<Window.Resources>
<ResourceDictionary>
<TextBlock x:Key="CursorGrab" Cursor="Resources/Cursors/grab.cur"/>
<TextBlock x:Key="CursorMagnify" Cursor="Resources/Cursors/magnify.cur"/>
</ResourceDictionary>
</Window.Resources>
Example of embedded cursor referenced in code:
代码中引用的嵌入式游标示例:
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
-本
回答by Ray Burns
There is an easier way than managing the cursor display yourself or using Visual Studio to construct lots of custom cursors.
有一种比自己管理光标显示或使用 Visual Studio 构建大量自定义光标更简单的方法。
If you have a FrameworkElement you can construct a Cursor from it using the following code:
如果您有 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);
}
Note that your FrameworkElement 's size must be a standard cursor size (eg 16x16 or 32x32), for example:
请注意,您的 FrameworkElement 的大小必须是标准光标大小(例如 16x16 或 32x32),例如:
<Grid x:Name="customCursor" Width="32" Height="32">
...
</Grid>
It would be used like this:
它将像这样使用:
someControl.Cursor = ConvertToCursor(customCursor, new Point(0.5, 0.5));
Obviously your FrameworkElement could be an <Image>control if you have an existing image, or you can draw anything you like using WPF's built-in drawing tools.
显然,<Image>如果您有现有图像,您的 FrameworkElement 可以是一个控件,或者您可以使用 WPF 的内置绘图工具绘制任何您喜欢的内容。
Note that details on the .cur file format can be found at ICO (file format).
请注意,可以在ICO(文件格式)中找到有关 .cur 文件格式的详细信息。
回答by kkCosmo
To use a custom cursor in XAML I altered the code Ben McIntosh provided slightly:
为了在 XAML 中使用自定义光标,我稍微修改了 Ben McIntosh 提供的代码:
<Window.Resources>
<Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>
To use the cursor just reference to the resource:
要使用游标,只需引用资源:
<StackPanel Cursor="{StaticResource OpenHandCursor}" />
回答by gjacobs
In case anyone is looking for a UIElement itself as a cursor, I combined the solutions of Rayand Arcturus:
如果有人正在寻找 UIElement 本身作为光标,我结合了Ray和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);
}
回答by Greg
A very easy way is to create the cursor within Visual Studio as a .cur file, and then add that to the projects Resources.
一种非常简单的方法是在 Visual Studio 中将光标创建为 .cur 文件,然后将其添加到项目资源中。
Then just add the following code when you want to assign the cursor:
然后在要分配光标时添加以下代码:
myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));
回答by paulmcguinness
I know this topic is a few years old now, but yesterday I wanted to load a custom cursor file from the project resources and ran into similar problems. I searched the internet for a solution and didn't find what I needed: to set the this.Cursorto a custom cursor stored in my resources folder in my project at runtime.
I've tried Ben's xaml solution, but didn't find it elegant enough.
PeterAllen stated:
我知道这个话题已经有几年了,但是昨天我想从项目资源中加载一个自定义光标文件并遇到了类似的问题。我在互联网上搜索了一个解决方案,但没有找到我需要的:this.Cursor在运行时将 设置为存储在我的项目的资源文件夹中的自定义光标。我试过 Ben 的 xaml 解决方案,但觉得它不够优雅。彼得艾伦说:
Lamely, a relative path or Pack URI will not work. If you need to load the cursor from a relative path or from a resource packed with your assembly, you will need to get a stream from the file and pass it in to the Cursor(Stream cursorStream) constructor. Annoying but true.
遗憾的是,相对路径或 Pack URI 将不起作用。如果需要从相对路径或与程序集打包在一起的资源加载游标,则需要从文件中获取流并将其传递给 Cursor(Stream cursorStream) 构造函数。烦人但真实。
I stumbled on a nice way to do this and resolves my problem:
我偶然发现了一个很好的方法来做到这一点并解决了我的问题:
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);
回答by Gábor
One more solution somewhat similar to Ray's but instead of slow and cumbersome pixel copying, this uses some Windows internals:
另一种解决方案有点类似于 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));
}
There is an extension method in the middle that I prefer to have in an extension class for such cases:
对于这种情况,我更喜欢在扩展类中使用中间的扩展方法:
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;
}
With all this, it's rather simple and straightforward.
有了这一切,它相当简单明了。
And, if you happen not to need to specify your own hotspot, you can even cut this shorter (you don't need the struct or the P/Invokes, either):
而且,如果您碰巧不需要指定自己的热点,您甚至可以将其缩短(您也不需要结构体或 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));
}
回答by palehorse
You could try this
你可以试试这个
<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />
回答by rudigrobler
Also check out Scott Hanselman's BabySmash (www.codeplex.com/babysmash). He used a more "brute force" method of hiding the windows cursor and showing his new cursor on a canvas and then moving the cursor to were the "real" cursor would have been
另请查看 Scott Hanselman 的 BabySmash (www.codeplex.com/babysmash)。他使用了一种更“蛮力”的方法来隐藏 Windows 光标并在画布上显示他的新光标,然后将光标移动到“真实”光标所在的位置。
Read more here: http://www.hanselman.com/blog/DeveloperDesigner.aspx
在此处阅读更多信息:http: //www.hanselman.com/blog/DeveloperDesigner.aspx

