Un-Antialiased Hand Cursor in Windows Forms Apps!

后端 未结 4 388
鱼传尺愫
鱼传尺愫 2020-12-06 02:46

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

相关标签:
4条回答
  • 2020-12-06 03:25

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-06 03:27

    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.

    0 讨论(0)
  • 2020-12-06 03:30

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-06 03:43

    Sorry for getting this old post back, but i also have some kind of solution for this. If you need to apply the systemcursor application wide without touching old controls, use this at applicationstart:

        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*/));
    
    0 讨论(0)
提交回复
热议问题