WPF中的自定义光标?

57

我想在WPF应用程序中使用图像或图标作为自定义光标。 我该怎么做?

15个回答

1

确保任何 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);

1
如果您正在使用Visual Studio,您可以:
  1. 新建光标文件
  2. 复制/粘贴图像
  3. 将其保存为.cur文件。

1

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

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


1

可能在Visual Studio 2017中有所改变,但我能够将.cur文件作为嵌入式资源引用:

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

0

这将使用附加属性将项目中存储的任何图像转换为光标。该图像必须编译为资源!

示例

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接