Search code examples
c#wpfnum-lock

How do I manually toggle the NumLock Key with C# in WPF?


I've been searching for a while and there are mostly results in C++ or other languages, and not C#. Things I've seen:

   keybd_event() // A c++ method that theoretically can be included with a DLL import, but hasn't worked in testing
   System.Windows.Forms.SendKeys.Send("{NUMLOCK}"}; // Forms namespace doesn't exist in Windows

Currently, I have code that executes every second or so to watch the state of numlock and update a graphic in my form accordingly. If a bool toggle is set, I also want it to force NumLock on:


        internal partial class Interop
        {
            public static int VK_NUMLOCK = 0x90;
            public static int VK_SCROLL = 0x91;
            public static int VK_CAPITAL = 0x14;
            public static int KEYEVENTF_EXTENDEDKEY = 0x0001; // If specified, the scan code was preceded by a prefix byte having the value 0xE0 (224).
            public static int KEYEVENTF_KEYUP = 0x0002; // If specified, the key is being released. If not specified, the key is being depressed.

            [DllImport("User32.dll", SetLastError = true)]
            public static extern void keybd_event(
                byte bVk,
                byte bScan,
                int dwFlags,
                IntPtr dwExtraInfo);

            [DllImport("User32.dll", SetLastError = true)]
            public static extern short GetKeyState(int nVirtKey);

            [DllImport("User32.dll", SetLastError = true)]
            public static extern short GetAsyncKeyState(int vKey);
        }

    private void watcher(object source, ElapsedEventArgs e) 
        {
            bool NumLock = (((ushort)GetKeyState(0x90)) & 0xffff) != 0;

            if (!NumLock && fixers.watchNumL)
            {
                // Force NumLock back on
                // Simulate a key press
                Interop.keybd_event((byte)0x90,0x45,Interop.KEYEVENTF_EXTENDEDKEY | 0,IntPtr.Zero);

                // Simulate a key release
                Interop.keybd_event((byte)0x90,0x45,Interop.KEYEVENTF_EXTENDEDKEY | Interop.KEYEVENTF_KEYUP,    IntPtr.Zero);
                NumLock = (((ushort)GetKeyState(0x90)) & 0xffff) != 0;
            }

            if (NumLock)
            {
                this.Dispatcher.Invoke(() =>
                {
                    fixerBoxes["NumL"].FixerImg.Source = new BitmapImage(new Uri(@"/graphics/num_lock_on.png", UriKind.Relative));
                    StatusBox.Text = "Num Lock ON";
                });
            }
            else {
                this.Dispatcher.Invoke(() =>
                {
                    fixerBoxes["NumL"].FixerImg.Source = new BitmapImage(new Uri(@"/graphics/num_lock_off.png", UriKind.Relative));
                    StatusBox.Text = "Num Lock OFF";
                });
            }

        }


        public MainWindow()
        {
            // Start the watcher
            System.Timers.Timer myTimer = new System.Timers.Timer();
            // Tell the timer what to do when it elapses
            myTimer.Elapsed += new ElapsedEventHandler(watcher);
            // Set it to go off every second
            myTimer.Interval = 1000;
            // And start it        
            myTimer.Enabled = true;

        }

Solution

  • Here is a class (with a library) that can do this for you. the library does much more, so it's maybe a bit overkill to use just for this. The approach uses the keybd_event function using pinvoke:

    // Simulate a key press
    Interop.keybd_event((byte)virtualKey,
        0x45,
        Interop.KEYEVENTF_EXTENDEDKEY | 0,
        IntPtr.Zero);
    
    // Simulate a key release
        Interop.keybd_event((byte)virtualKey,
        0x45,
        Interop.KEYEVENTF_EXTENDEDKEY | Interop.KEYEVENTF_KEYUP,
        IntPtr.Zero);
    

    Pressing and releasing the button changes the state of the LED. virtualKey is one of the VK_ constants.

    Here are the declarations:

    internal partial class Interop
    {
        public static int VK_NUMLOCK = 0x90;
        public static int VK_SCROLL = 0x91;
        public static int VK_CAPITAL = 0x14;
        public static int KEYEVENTF_EXTENDEDKEY = 0x0001; // If specified, the scan code was preceded by a prefix byte having the value 0xE0 (224).
        public static int KEYEVENTF_KEYUP = 0x0002; // If specified, the key is being released. If not specified, the key is being depressed.
    
        [DllImport("User32.dll", SetLastError = true)]
        public static extern void keybd_event(
            byte bVk,
            byte bScan,
            int dwFlags,
            IntPtr dwExtraInfo);
    
        [DllImport("User32.dll", SetLastError = true)]
        public static extern short GetKeyState(int nVirtKey);
    
        [DllImport("User32.dll", SetLastError = true)]
        public static extern short GetAsyncKeyState(int vKey);
    }