我想在WPF应用程序中使用图像或图标作为自定义光标。 我该怎么做?
确保任何 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);
另外,也可以查看Scott Hanselman的BabySmash(www.codeplex.com/babysmash)。他使用了一种更加“蛮力”的方法来隐藏Windows光标,并在画布上显示他的新光标,然后将光标移动到“真实”光标所在的位置
在此处阅读更多: http://www.hanselman.com/blog/DeveloperDesigner.aspx
可能在Visual Studio 2017中有所改变,但我能够将.cur文件作为嵌入式资源引用:
<Setter
Property="Cursor"
Value="/assembly-name;component/location-name/curser-name.cur" />
这将使用附加属性将项目中存储的任何图像转换为光标。该图像必须编译为资源!
示例
<Button MyLibrary:FrameworkElementExtensions.Cursor=""{MyLibrary:Uri MyAssembly, MyImageFolder/MyImage.png}""/>
FrameworkElementExtensions
using System;
using System.Windows;
using System.Windows.Media;
public static class FrameworkElementExtensions
{
#region Cursor
public static readonly DependencyProperty CursorProperty = DependencyProperty.RegisterAttached("Cursor", typeof(Uri), typeof(FrameworkElementExtensions), new UIPropertyMetadata(default(Uri), OnCursorChanged));
public static Uri GetCursor(FrameworkElement i) => (Uri)i.GetValue(CursorProperty);
public static void SetCursor(FrameworkElement i, Uri input) => i.SetValue(CursorProperty, input);
static void OnCursorChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement frameworkElement)
{
if (GetCursor(frameworkElement) != null)
frameworkElement.Cursor = new ImageSourceConverter().ConvertFromString(((Uri)e.NewValue).OriginalString).As<ImageSource>().Bitmap().Cursor(0, 0).Convert();
}
}
#endregion
}
ImageSourceExtensions
using System.Drawing;
using System.Windows.Media;
using System.Windows.Media.Imaging;
public static class ImageSourceExtensions
{
public static Bitmap Bitmap(this ImageSource input) => input.As<BitmapSource>().Bitmap();
}
BitmapSourceExtensions
using System.IO;
using System.Windows.Media.Imaging;
public static class BitmapSourceExtensions
{
public static System.Drawing.Bitmap Bitmap(this BitmapSource input)
{
if (input == null)
return null;
System.Drawing.Bitmap result;
using (var outStream = new MemoryStream())
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(input));
encoder.Save(outStream);
result = new System.Drawing.Bitmap(outStream);
}
return result;
}
}
Bitmap扩展
using System;
using System.Drawing;
using System.Runtime.InteropServices;
public static class BitmapExtensions
{
[StructLayout(LayoutKind.Sequential)]
public struct ICONINFO
{
/// <summary>
/// Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies an icon; FALSE specifies a cursor.
/// </summary>
public bool fIcon;
/// <summary>
/// Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot spot is always in the center of the icon, and this member is ignored.
/// </summary>
public Int32 xHotspot;
/// <summary>
/// Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot spot is always in the center of the icon, and this member is ignored.
/// </summary>
public Int32 yHotspot;
/// <summary>
/// (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, this bitmask is formatted so that the upper half is the icon AND bitmask and the lower half is the icon XOR bitmask. Under this condition, the height should be an even multiple of two. If this structure defines a color icon, this mask only defines the AND bitmask of the icon.
/// </summary>
public IntPtr hbmMask;
/// <summary>
/// (HBITMAP) Handle to the icon color bitmap. This member can be optional if this structure defines a black and white icon. The AND bitmask of hbmMask is applied with the SRCAND flag to the destination; subsequently, the color bitmap is applied (using XOR) to the destination by using the SRCINVERT flag.
/// </summary>
public IntPtr hbmColor;
}
[DllImport("user32.dll")]
static extern IntPtr CreateIconIndirect([In] ref ICONINFO piconinfo);
[DllImport("user32.dll")]
static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyIcon(IntPtr hIcon);
public static System.Windows.Forms.Cursor Cursor(this Bitmap input, int hotX, int hotY)
{
ICONINFO Info = new ICONINFO();
IntPtr Handle = input.GetHicon();
GetIconInfo(Handle, out Info);
Info.xHotspot = hotX;
Info.yHotspot = hotY;
Info.fIcon = false;
IntPtr h = CreateIconIndirect(ref Info);
return new System.Windows.Forms.Cursor(h);
}
}
CursorExtensions
using Microsoft.Win32.SafeHandles;
public static class CursorExtensions
{
public static System.Windows.Input.Cursor Convert(this System.Windows.Forms.Cursor Cursor)
{
SafeFileHandle h = new SafeFileHandle(Cursor.Handle, false);
return System.Windows.Interop.CursorInteropHelper.Create(h);
}
}
作为
public static Type As<Type>(this object input) => input is Type ? (Type)input : default;
Uri
using System;
using System.Windows.Markup;
public class Uri : MarkupExtension
{
public string Assembly { get; set; } = null;
public string RelativePath { get; set; }
public Uri(string relativePath) : base()
{
RelativePath = relativePath;
}
public Uri(string assembly, string relativePath) : this(relativePath)
{
Assembly = assembly;
}
static Uri Get(string assemblyName, string relativePath) => new Uri($"pack://application:,,,/{assemblyName};component/{relativePath}", UriKind.Absolute);
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Assembly == null)
return new System.Uri(RelativePath, UriKind.Relative);
return Get(Assembly, RelativePath);
}
}