1

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;

        }
not_a_generic_user
  • 1,906
  • 2
  • 19
  • 34

1 Answers1

1

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

PMF
  • 14,535
  • 3
  • 23
  • 49
  • The [docs](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event) say *"Note This function has been superseded. Use [`SendInput`](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendinput) instead."* – Charlieface Jan 13 '22 at 17:16
  • 1
    Possible that there are other/better alternatives. But this works, and winapi functions are never removed. – PMF Jan 13 '22 at 17:34
  • Thank you! I can manage to detect the state of Num Lock and I can change its state manually with the above, but it's not auto-healing like I was hoping. Something is disconnected between the watching function and the toggle function. I've updated my code above to include my main function (initializes the watcher), the watcher function, and your code. – not_a_generic_user Jan 14 '22 at 15:39
  • If I understand the documentation of the GetKeyState function correctly, https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeystate you have to check only the lowest bit of the return value (the toggle bit). The return value contains both the information whether the key is currently enabled/disabled (the LED is on or off) as well as whether the button is currently pressed. – PMF Jan 14 '22 at 15:48
  • Ding! That was the issue, thank you! The test now reads: bool NumLock = (GetKeyState(0x90) & 0x0001) != 0; – not_a_generic_user Jan 28 '22 at 15:19