Windows窗体应用程序中未抗锯齿的手形光标

8
好的,您知道在Windows Vista和Windows 7中,微软更改了手形光标(当您悬停在超链接上时显示的光标),并添加了更多细节,使其抗锯齿并且边缘平滑?那么为什么在Windows Forms应用程序中不是这样呢?
我已经厌倦了看着一个看起来像是由穴居人绘制的糟糕手形光标。有没有一种编程方法可以让它显示实际安装在系统中的光标?我查看了我的Windows目录中的Cursors文件夹,但旧的手形光标甚至都不在那里!那么为什么WinForms仍然在使用旧的光标?我该如何“升级”它呢?
5个回答

13

是的,WinForms控件仍然使用旧式手形光标,就像Windows 98/2000一样。它缺乏与Aero光标中包含的相同的平滑效果。这是因为.NET Framework包含了自己硬编码的光标,它使用这个光标而不是系统默认的光标。我想这是因为早期版本的.NET针对的操作系统是像Windows 95这样的,并没有捆绑此光标,但我还没有进行考古证明。

幸运的是,很容易强制它使用正确的光标。你只需要告诉操作系统你想要使用默认手型光标,然后无论用户在什么版本的Windows上运行程序,甚至如果他们将鼠标光标从默认主题更改,都会选择正确的光标。

最简单的方法是子类化现有控件,重写WndProc函数来拦截WM_SETCURSOR消息,并告诉它使用系统的IDC_HAND光标。你只需要使用一点点P/Invoke技巧。

以下代码是使用控件的示例:

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

嗯,我...感到震惊。 恐惧。 困惑。宽慰。那么,我要为应用程序中想要拥有抗锯齿光标的每个控件创建子类? 这不是有点过度吗? - 顺便说一下,感谢您的代码,它解决了问题! :) - βӔḺṪẶⱫŌŔ
1
@βӔḺṪẶⱫŌŔ:不需要。其他光标都能正常工作。Windows应用程序需要显示手型光标的情况非常罕见。它唯一被使用的场景就是 LinkLabel。因此,您只需要创建一个自定义替代 LinkLabel 控件,然后在应用程序中随处使用即可。 - Cody Gray
没错,那就是我的意思。但是我还有一个PictureBox(客户的标志),点击它会带他们到他们的网站,所以我也要显示手形光标。 - βӔḺṪẶⱫŌŔ
1
@βӔḺṪẶⱫŌŔ:好的,所以你有两个自定义控件。一个是CustomLinkLabel,另一个是LinkPictureBox。对我来说似乎并不难。我已经为几乎所有标准控件编写了增强功能,并在所有项目中反复重用它们。 - Cody Gray

11

不好意思,我给一年前的帖子复活了!!!

在尝试了原始解决方案并查看了反射的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;
            }
        }
    }
}

这个类实际上做到了我们想要的:在控件的链接区域内显示适当的系统手形光标,而且不会闪烁。


2
我建议不要在每次鼠标移动时创建新的光标。这似乎是一种不必要的内存泄漏。相反,我会创建一个静态字段来设置一个静态只读光标变量,然后在需要时引用它。 - test
1
你是对的。真不敢相信我没看到那个问题... 我已经编辑了代码来修复它。 - Hamid Sadeghian
一个类似但更好的解决方案是响应WM_SETCURSOR消息来设置光标。这就是我的答案所建议的。当系统只在需要时发送特定于此目的的完美消息时,没有理由在响应WM_MOUSEMOVE(OnMouseMove)时执行此操作。 - Cody Gray
1
正如Hamido-san所说,他的答案在控件的LinkArea内部可以正确工作,并且如果LinkLabel未设置为AutoSized,它也可以正确工作。而WndProc解决方案则无法通过这两个测试。 - Michael Csikos

2

抱歉,翻出了这篇旧帖子,但我也有一种解决方案。

如果您需要在不触碰旧控件的情况下应用系统光标于整个应用程序中,请在应用程序启动时使用以下方法:

<!--最初的回答-->

    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*/));

非常棒的最小代码解决方案。对于新手来说,这是在Visual Studio的Program.cs文件中。 - Jonas

2
这篇文章解决了其他文章的问题:
  • 对于链接位置的尊重,只有当鼠标指针悬停在链接上时才显示手形光标
  • 鼠标移动时不会闪烁
需要将光标更改为系统手型光标。为此,您需要处理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);
        }
    }
}

0

在不创建新控件的情况下,我们需要更改控件的光标并创建自定义链接标签,否则它将无法工作。我们通过添加标签、更改字体下划线和更改前景色以及添加单击事件来创建自定义链接标签。

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代码,您可以使用在线转换器

编辑:有一些代码丢失了


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