Un-Antialiased Hand Cursor in Windows Forms Apps!

2019-01-15 02:13发布

问题:

Okay, so you know how in Windows Vista and Windows 7 MS changed the Hand Cursor (the one that shows up when you hover over a hyperlink), and added more detail to it so it's antialiased and nice and smooth around the edges?

Well, why isn't it like that in Windows Forms apps?

I'm sick off looking at a crappy hand cursor that looks like it was drawn by a caveman, is there a way to programmatically tell it to display the one that's actually installed in the system? I looked in the Cursors folder in my Windows directory, and the old hand cursor isn't even there! So why is WinForms still using the old one? How can I 'upgrade' it?

回答1:

Yes, the WinForms controls still use the old-school hand cursor, as shipped with Windows 98/2000. It lacks the anti-aliasing effects that the one included with the Aero cursors does. This is because the .NET Framework includes its own hard-coded cursor, which it uses instead of the system default. I presume this is because early versions of .NET were targeting operating systems like Windows 95 that didn't come bundled with this cursor, but haven't done the archaeology to prove it.

Fortunately, it's easy enough to force it to use the right one. You just have to tell the operating system you want it to use the default hand cursor, and then it will be correct no matter what version of Windows the user runs your program on, and even if they've changed their mouse cursors from the default theme.

The simplest way of doing that is to subclass the existing control, override the WndProc function to intercept the WM_SETCURSOR message, and tell it to use the system IDC_HAND cursor. You just need a little bit of P/Invoke magic.

The following code is an example of how that might look using the LinkLabel control:

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


回答2:

Excuse me for resurrecting a year-old thread!!!

After messing around with the original solution and taking a look at the reflected LinkLabel source code, I "finally" found a quick yet clean way of doing it :

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

This class actually does what we want: It shows the proper system hand cursor without flickering and does this only on the LinkArea of the control.



回答3:

This post solves problems of the other posts:

  • It respects to the link location and shows the hand just when cursor is on link
  • It doesn't flicker on mouse move

You need to change the cursor to system hand cursor. To do so, you need to handle WM_SETCURSOR and check if OverrideCursor is Cursors.Hand then change it to the system cursor by calling 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);
        }
    }
}