Windows 窗体应用中的无锯齿手形光标

本文关键字:光标 窗体 应用 Windows | 更新日期: 2023-09-27 17:56:27

好的,所以你知道在Windows Vista和Windows 7 MS中如何更改手形光标(当您将鼠标悬停在超链接上时显示的光标),并为其添加更多细节,使其具有抗锯齿性,边缘美观且平滑?

那么,为什么在Windows窗体应用程序中不是这样呢?

我厌倦了看着一个蹩脚的手光标,看起来像是穴居人画的。

有没有办法以编程方式告诉它显示系统中实际安装的那个?我查看了Windows目录中的"光标"文件夹,旧的手形光标甚至不在那里!那么为什么WinForms仍然使用旧的呢?如何"升级"它?

Windows 窗体应用中的无锯齿手形光标

是的,WinForms 控件仍然使用老式的手形光标,如 Windows 98/2000 附带的那样。它缺乏 Aero 光标中包含的抗锯齿效果。这是因为 .NET Framework 包含自己的硬编码游标,它使用该游标而不是系统默认值。我认为这是因为早期版本的.NET针对的是像Windows 95这样的操作系统,这些操作系统没有与此光标捆绑在一起,但还没有考古来证明这一点。

幸运的是,迫使它使用正确的方法很容易。您只需要告诉操作系统您希望它使用默认的手形光标,然后无论用户在哪个版本的Windows上运行您的程序,即使他们已经从默认主题更改了鼠标光标,它都是正确的。

最简单的方法是对现有控件进行子类化,重写 WndProc 函数以截获WM_SETCURSOR消息,并告诉它使用系统IDC_HAND游标。你只需要一点点P/Invoke魔法。

下面的代码是使用 LinkLabel 控件的外观示例:

public class LinkLabelEx : LinkLabel
{
    private const int WM_SETCURSOR = 0x0020;
    private const int IDC_HAND = 32649;
    [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
    [DllImport("user32.dll", CharSet=CharSet.Auto)]
    private static extern IntPtr SetCursor(IntPtr hCursor);
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_SETCURSOR)
        {
            // Set the cursor to use the system hand cursor
            SetCursor(LoadCursor(IntPtr.Zero, IDC_HAND));
            // Indicate that the message has been handled
            m.Result = IntPtr.Zero;
            return;
        }
        base.WndProc(ref m);
    }
}

请原谅我复活了一年前的线程!!

在弄乱了原始解决方案并查看了反映的 LinkLabel 源代码之后,我"终于"找到了一种快速而干净的方法:

using System.Runtime.InteropServices;
namespace System.Windows.Forms {
    public class LinkLabelEx : LinkLabel {
        private const int IDC_HAND = 32649;
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
        private static readonly Cursor SystemHandCursor = new Cursor(LoadCursor(IntPtr.Zero, IDC_HAND));
        protected override void OnMouseMove(MouseEventArgs e) {
            base.OnMouseMove(e);
            // If the base class decided to show the ugly hand cursor
            if(OverrideCursor == Cursors.Hand) {
                // Show the system hand cursor instead
                OverrideCursor = SystemHandCursor;
            }
        }
    }
}

这个类实际上做了我们想要的:它显示正确的系统手形光标而不闪烁,并且仅在控件的 LinkArea 上执行此操作。

这篇文章解决了其他帖子的问题:

  • 它尊重链接位置,并在光标位于链接上时显示手
  • 鼠标移动时不会闪烁

您需要将光标更改为系统手形光标。为此,您需要处理WM_SETCURSOR并检查OverrideCursor是否Cursors.Hand然后通过调用SetCursor将其更改为系统光标:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyLinkLabel : LinkLabel
{
    const int IDC_HAND = 32649;
    const int WM_SETCURSOR = 0x0020;
    const int HTCLIENT = 1;
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
    [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
    static extern IntPtr SetCursor(HandleRef hcursor);
    static readonly Cursor SystemHandCursor = 
        new Cursor(LoadCursor(IntPtr.Zero, IDC_HAND));
    protected override void WndProc(ref Message msg)
    {
        if (msg.Msg == WM_SETCURSOR)
            WmSetCursor(ref msg);
        else
            base.WndProc(ref msg);
    }
    void WmSetCursor(ref Message m)
    {
        if (m.WParam == (IsHandleCreated ? Handle : IntPtr.Zero) &&
           (unchecked((int)(long)m.LParam) & 0xffff) == HTCLIENT) {
            if (OverrideCursor != null) {
                if (OverrideCursor == Cursors.Hand)
                    SetCursor(new HandleRef(SystemHandCursor, SystemHandCursor.Handle));
                else
                    SetCursor(new HandleRef(OverrideCursor, OverrideCursor.Handle));
            }
            else {
                SetCursor(new HandleRef(Cursor, Cursor.Handle));
            }
        }
        else {
            DefWndProc(ref m);
        }
    }
}

很抱歉找回这个旧帖子,但我也有某种解决方案。如果您需要在不接触旧控件的情况下应用系统光标应用程序,请在应用程序启动时使用以下命令:

    private static void TrySetCursorsDotHandToSystemHandCursor()
    {
        try
        {
            typeof(Cursors).GetField("hand", BindingFlags.Static | BindingFlags.NonPublic)
                           .SetValue(null, SystemHandCursor);
        }
        catch { }
    }
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
    private static readonly Cursor SystemHandCursor = new Cursor(LoadCursor(IntPtr.Zero, 32649 /*IDC_HAND*/));

在不创建新控件的情况下执行此操作,我们需要更改控件的光标并创建自定义链接标签,否则它将不起作用我们通过添加更改字体下划线并更改其前景色的标签来创建自定义链接标签,并添加点击事件

Private Const IDC_HAND As Integer = 32649
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function LoadCursor(ByVal hInstance As IntPtr, ByVal lpCursorName As Integer) As IntPtr
End Function
Private Shared ReadOnly SystemHandCursor As Cursor = New Cursor(LoadCursor(IntPtr.Zero, IDC_HAND))
    
'add the cursor to custom linklabel
CustomLinkLabel1.Cursor = SystemHandCursor

抱歉只有 VB .NET 代码,您可以使用在线转换器编辑:缺少一些代码