Click here to Skip to main content
15,893,381 members
Articles / Desktop Programming / Win32

Toggle Keys Controller

Rate me:
Please Sign up or sign in to vote.
4.89/5 (27 votes)
3 Sep 2009CPOL6 min read 50.1K   1.4K   50  
A class that enables you to control and monitor the toggle keys such as CapsLock, NumLock, and ScrollLock.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace Keyboard
{
    /// <summary>
    /// Controls and reports changes in state of toggle keys such as
    /// CapsLock, NumLock and ScrollLock.
    /// </summary>
    public class ToggleKeysController
    {
        #region Events and Delegates

        private delegate void KeyCheckDelayHandler(AsyncOperation asyncOperation, object state);

        /// <summary>
        /// Raised when a change in the CapsLock state occurs.
        /// </summary>
        public event EventHandler CapsLockStateChanged;
        /// <summary>
        /// Raised when a change in the NumLock state occurs.
        /// </summary>
        public event EventHandler NumLockStateChanged;
        /// <summary>
        /// Raised when a change in the ScrollLock state occurs.
        /// </summary>
        public event EventHandler ScrollLockStateChanged;
        /// <summary>
        /// Raised when a change in state of a monitored key occurs.
        /// </summary>
        public event EventHandler<ToggleKeyStateChangedEventArgs> ToggleKeyStateChanged;

        #endregion

        #region Fields

        private IntPtr hookHandle;
        private LowLevelKeyboardProc hookProc;
        private bool _CapsLockOn;
        private bool _NumLockOn;
        private bool _ScrollLockOn;

        #endregion

        #region Constructor and Destructor

        /// <summary>
        /// Creates a new instance of the Keyboard.ToggleKeysConstroller class.
        /// </summary>
        public ToggleKeysController()
        {
            hookProc = KeyHookCallback;
            using (Process currentProcess = Process.GetCurrentProcess())
            {
                using (ProcessModule currentModule = currentProcess.MainModule)
                {
                    hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, GetModuleHandle(currentModule.ModuleName), 0);
                }
            }
            LoadToggleKeys();
        }
        ~ToggleKeysController()
        {
            Dispose(false);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the state of the CapsLock key.
        /// </summary>
        public bool CapsLockOn
        {
            get { return _CapsLockOn; }
            set
            {
                if (value != _CapsLockOn)
                    ToggleKey(ToggleKeys.CapsLock);
            }
        }
        /// <summary>
        /// Gets or sets the state of the NumLock key.
        /// </summary>
        public bool NumLockOn
        {
            get { return _NumLockOn; }
            set
            {
                if (value != _NumLockOn)
                    ToggleKey(ToggleKeys.NumLock);
            }
        }
        /// <summary>
        /// Gets or sets the state of the ScrollLock key.
        /// </summary>
        public bool ScrollLockOn
        {
            get { return _ScrollLockOn; }
            set
            {
                if (value != _ScrollLockOn)
                    ToggleKey(ToggleKeys.ScrollLock);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Disposes of all resources used by this Keyboard.ToggleKeysController instance.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Disposes of unmanaged resources used by this Keyboard.ToggleKeysController instance
        /// and optionally any managed resources.
        /// </summary>
        /// <param name="disposing">Whether to dispose of managed resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
            }
            if (hookHandle != IntPtr.Zero)
            {
                UnhookWindowsHookEx(hookHandle);
                hookHandle = IntPtr.Zero;
            }
        }
        private void LoadToggleKeys()
        {
            _CapsLockOn = GetCapsLock();
            _NumLockOn = GetNumLock();
            _ScrollLockOn = GetScrollLock();
        }
        private bool GetCapsLock()
        {
            return (GetKeyState(VK_CAPITAL) & 1) == 1;
        }
        private bool GetNumLock()
        {
            return (GetKeyState(VK_NUMLOCK) & 1) == 1;
        }
        private bool GetScrollLock()
        {
            return (GetKeyState(VK_SCROLL) & 1) == 1;
        }
        private void KeyCheckDelay(AsyncOperation asyncOperation, object state)
        {
            Thread.Sleep(5);
            asyncOperation.PostOperationCompleted(
                new SendOrPostCallback(KeyCheckDo),
                state);
        }
        private void KeyCheckDo(object state)
        {
            byte key = (byte)state;
            switch (key)
            {
                case VK_CAPITAL:
                    bool current = GetCapsLock();
                    if (current != _CapsLockOn)
                    {
                        _CapsLockOn = current;
                        OnCapsLockStateChanged(EventArgs.Empty);
                        OnToggleStateChanged(new ToggleKeyStateChangedEventArgs(ToggleKeys.CapsLock, current));
                    }
                    break;
                case VK_NUMLOCK:
                    current = GetNumLock();
                    if (current != _NumLockOn)
                    {
                        _NumLockOn = current;
                        OnNumLockStateChanged(EventArgs.Empty);
                        OnToggleStateChanged(new ToggleKeyStateChangedEventArgs(ToggleKeys.NumLock, current));
                    }
                    break;
                case VK_SCROLL:
                    current = GetScrollLock();
                    if (current != _ScrollLockOn)
                    {
                        _ScrollLockOn = current;
                        OnScrollLockStateChanged(EventArgs.Empty);
                        OnToggleStateChanged(new ToggleKeyStateChangedEventArgs(ToggleKeys.ScrollLock, current));
                    }
                    break;
            }
        }
        private IntPtr KeyHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                byte key = Marshal.ReadByte(lParam);
                if (key == VK_CAPITAL || key == VK_NUMLOCK || key == VK_SCROLL)
                    new KeyCheckDelayHandler(KeyCheckDelay).BeginInvoke(
                        AsyncOperationManager.CreateOperation(null), key, null, null);
            }
            return CallNextHookEx(hookHandle, nCode, wParam, lParam);
        }
        /// <summary>
        /// Raises the CapsLockStateChanged event.
        /// </summary>
        /// <param name="e">A System.EventArgs instance.</param>
        protected virtual void OnCapsLockStateChanged(EventArgs e)
        {
            EventHandler eh = CapsLockStateChanged;
            if (eh != null)
                eh(this, e);
        }
        /// <summary>
        /// Raises the NumLockStateChanged event.
        /// </summary>
        /// <param name="e">A System.EventArgs instance.</param>
        protected virtual void OnNumLockStateChanged(EventArgs e)
        {
            EventHandler eh = NumLockStateChanged;
            if (eh != null)
                eh(this, e);
        }
        /// <summary>
        /// Raises the ScrollLockStateChanged event.
        /// </summary>
        /// <param name="e">A System.EventArgs instance.</param>
        protected virtual void OnScrollLockStateChanged(EventArgs e)
        {
            EventHandler eh = ScrollLockStateChanged;
            if (eh != null)
                eh(this, e);
        }
        /// <summary>
        /// Raises the ToggleStateChanged event.
        /// </summary>
        /// <param name="e">A Keyboard.ToggleKeyStateChangedEventArgs instance.</param>
        protected virtual void OnToggleStateChanged(ToggleKeyStateChangedEventArgs e)
        {
            EventHandler<ToggleKeyStateChangedEventArgs> eh = ToggleKeyStateChanged;
            if (eh != null)
                eh(this, e);
        }
        /// <summary>
        /// Toggles the state of the specified key.
        /// </summary>
        /// <param name="key">The ToggleKeys.Key to toggle.</param>
        public void ToggleKey(ToggleKeys key)
        {
            keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN, 0);
            keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
        }
        /// <summary>
        /// Returns a string representation of this Keyboard.ToggleKeysController instance.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("CapsLockOn: {0}, NumLockOn: {1}, ScrollLockOn: {2}",
                CapsLockOn, NumLockOn, ScrollLockOn);
        }

        #endregion

        #region Interop

        #region Constants

        private const int KEYEVENTF_EXTENDEDKEY = 0x0001;
        private const int KEYEVENTF_KEYDOWN = 0x0000;
        private const int KEYEVENTF_KEYUP = 0x0002;
        internal const byte VK_CAPITAL = 0x14;
        internal const byte VK_NUMLOCK = 0x90;
        internal const byte VK_SCROLL = 0x91;
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;

        #endregion Constants

        #region Delegates and Functions

        // http://msdn.microsoft.com/en-us/library/ms644985(VS.85).aspx
        /// <summary>
        /// The system calls this function every time a new keyboard input event is about to be posted into a thread input queue.
        /// </summary>
        /// <param name="nCode">A code the hook procedure uses to determine how to process the message.</param>
        /// <param name="wParam">Identifier of the keyboard message.</param>
        /// <param name="lParam">Pointer to a KBDLLHOOKSTRUCT structure.
        /// See http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx</param>
        /// <returns>The value returned by CallNextHookEx.</returns>
        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

        // http://msdn.microsoft.com/en-us/library/ms644974(VS.85).aspx
        /// <summary>
        /// Passes the hook information to the next hook procedure in the current hook chain.
        /// </summary>
        /// <param name="hhk">Handle to the current hook.</param>
        /// <param name="nCode">The hook code passed to the current hook procedure.</param>
        /// <param name="wParam">The wParam value passed to the current hook procedure.</param>
        /// <param name="lParam">The lParam value passed to the current hook procedure.</param>
        /// <returns>This value is returned by the next hook procedure in the chain.
        /// The current hook procedure must also return this value.</returns>
        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        // http://msdn.microsoft.com/en-us/library/ms646301(VS.85).aspx
        /// <summary>
        /// Retrieves the status of the specified virtual key.
        /// </summary>
        /// <param name="nVirtKey">A virtual key.</param>
        /// <returns>The status of the specified virtual key/</returns>
        [DllImport("user32.dll")]
        private static extern short GetKeyState(byte nVirtKey);
        // http://msdn.microsoft.com/en-us/library/ms885630.aspx
        /// <summary>
        /// returns a module handle for the specified module.
        /// </summary>
        /// <param name="lpModuleName">String that contains the name of the module.</param>
        /// <returns>A handle to the specified module.</returns>
        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
        // http://msdn.microsoft.com/en-us/library/ms646304(VS.85).aspx
        /// <summary>
        /// Synthesizes a keystroke.
        /// </summary>
        /// <param name="bVk">A virtual-key code.</param>
        /// <param name="bScan">A hardware scan code for the key.</param>
        /// <param name="dwFlags">Various aspects of function operation.</param>
        /// <param name="dwExtraInfo">An additional value associated with the key stroke.</param>
        [DllImport("user32.dll")]
        private static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
        // http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx
        /// <summary>
        /// Installs an application-defined hook procedure into a hook chain.
        /// </summary>
        /// <param name="idHook">The type of hook procedure to be installed.</param>
        /// <param name="lpfn">Pointer to the hook procedure.</param>
        /// <param name="hMod">Handle to the module containing the hook procedure pointed to by the lpfn parameter.</param>
        /// <param name="dwThreadId">The identifier of the thread with which the hook procedure is to be associated.</param>
        /// <returns>The handle to the hook procedure.</returns>
        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, int dwThreadId);
        // http://msdn.microsoft.com/en-us/library/ms644993(VS.85).aspx
        /// <summary>
        /// Removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
        /// </summary>
        /// <param name="hhk">Handle to the hook to be removed.</param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        #endregion Delegates and Functions

        #endregion Interop
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Dave Meadowcroft
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions