Click here to Skip to main content
Click here to Skip to main content

Filtering Special Keys Using C#

, 3 Mar 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Filter out unwanted keys and key combinations: Alt+F4, Ctrl+F4, Alt+Tab, Ctrl+Esc, Windows keys, Windows key shortcuts, and Windows hotkeys in C# by capturing Win32 low-level keyboard events.

Introduction

This article demonstrates capturing and filtering any key or key combination (except CTRL+ALT+DEL) in a C# application. This is useful if you'd like to prohibit a user from using: ALT+F4 to close the application, Windows key or CTRL+Esc to access the Start Menu, CTRL+F4 to close a tab, ALT+Tab to switch applications, and Windows hotkeys like Windows+E, Windows+D, and Windows+R.

This article demonstrates using Win32 system hooks in C# to capture all keystrokes and key combinations and then hide any keys that aren't specifically permitted for this application.

Background

The KeyCapture class presented in this article uses Win32 calls to register a low-level keyboard event hook with Windows that gives us more control over what keyboard events are processed than can be done through the keyboard events available in the .NET Framework. We'll register (and unregister) a callback that allows us to receive all low-level keyboard events and then permit keys that are on our 'permitted' list and quietly discard keys that are not on the list.

This article and code was based on the excellent blog article written by Agha Usman Ahmed that can be found here: http://geekswithblogs.net/aghausman/archive/2009/04/26/disable-special-keys-in-win-app-c.aspx.

Many thanks to Agha for posting his article and allowing me to use his code in this article.

Using the code

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsKeyCapture
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// Allows filtering of any keys, including special
    /// keys like CTRL, ALT, and Windows keys,
    /// Win32 windows hooks.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><remarks>
</span>    /// Original code example from:
    /// http://geekswithblogs.net/aghausman/archive/
    ///     2009/04/26/disable-special-keys-in-win-app-c.aspx
    /// on 27-FEB-2010
    /// <span class="code-SummaryComment"></remarks>
</span>    public class KeyCapture
    {
      /// <span class="code-SummaryComment"><summary>
</span>      /// Information about the low-level keyboard input event.
      /// <span class="code-SummaryComment"></summary>
</span>      [StructLayout(LayoutKind.Sequential)]
      private struct KBDLLHOOKSTRUCT
      {
        public Keys key;
        public int scanCode;
        public int flags;
        public int time;
        public IntPtr extra;
      }

      private const int WH_KEYBOARD_LL = 13;

      // System level function used to hook and unhook keyboard input
      private delegate IntPtr LowLevelKeyboardProc(int nCode, 
                       IntPtr wParam, IntPtr lParam);

      [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
      private static extern IntPtr SetWindowsHookEx(int id, 
              LowLevelKeyboardProc callback, IntPtr hMod, uint dwThreadId);

      [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
      private static extern bool UnhookWindowsHookEx(IntPtr hook);

      [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
      private static extern IntPtr CallNextHookEx(IntPtr hook, 
                            int nCode, IntPtr wp, IntPtr lp);

      [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
      private static extern IntPtr GetModuleHandle(string name);

      [DllImport("user32.dll", CharSet = CharSet.Auto)]
      private static extern short GetAsyncKeyState(Keys key);

      private static IntPtr ptrHook;
      private static LowLevelKeyboardProc objKeyboardProcess;
      private static bool _FilterKeys = true;

      /// <span class="code-SummaryComment"><summary>
</span>      /// The keys that this key capture class
      /// will accept when key filtering is enabled.
      ///
      </summary>
      public static List<Keys> PermittedKeys = new List<Keys>(
        new Keys[]
        { // Alphanumeric keys.
          Keys.A, Keys.B, Keys.C, Keys.D, Keys.E, Keys.F, Keys.G, Keys.H,
          Keys.I, Keys.J, Keys.K, Keys.L, Keys.M, Keys.N, Keys.O, Keys.P,
          Keys.Q, Keys.R, Keys.S, Keys.T, Keys.U, Keys.V, Keys.W, Keys.X,
          Keys.Y, Keys.Z,
          Keys.D0, Keys.D1, Keys.D2, Keys.D3, Keys.D4, Keys.D5, Keys.D6,
          Keys.D7, Keys.D8, Keys.D9,
          Keys.NumPad0, Keys.NumPad1, Keys.NumPad2, Keys.NumPad3, Keys.NumPad4,
          Keys.NumPad5, Keys.NumPad6, Keys.NumPad7, Keys.NumPad8, Keys.NumPad9,
          Keys.Decimal,

          // Punctuation and other keys
          Keys.Enter, Keys.Space,
          Keys.Subtract, Keys.Add, Keys.Divide, Keys.Multiply,
          Keys.OemOpenBrackets, Keys.OemCloseBrackets, Keys.Oemcomma, Keys.OemPeriod,
          Keys.Oemplus, Keys.OemMinus, Keys.OemQuestion, Keys.OemQuotes,
          Keys.OemSemicolon, Keys.Oemtilde, Keys.OemBackslash,

          // Editing keys
          Keys.Delete, Keys.Back,
          Keys.LShiftKey, Keys.RShiftKey, Keys.Shift, Keys.ShiftKey,

          // Navigation keys
          Keys.Up, Keys.Down, Keys.Left, Keys.Right, Keys.PageUp, Keys.PageDown,
          Keys.Home, Keys.End,
      });

      /// <summary>
      /// Capture keystrokes and filter which key events are permitted to continue.
      /// </summary>
      private static IntPtr CaptureKey(int nCode, IntPtr wp, IntPtr lp)
      {
        // If the nCode is non-negative, filter the key stroke.
        if (nCode >

The KeyCapture class allows filtering of keys using Win32 hooks to capture low-level keyboard events. To use the KeyCapture class, place the 'permitted' keys into the PermittedKeys list using System.Windows.Forms.Keys and set KeyCapture.FilterKeys to true.

KeyCapture.PermittedKeys = new List<Keys>(new Keys[] 
    { Keys.A, Keys.B, Keys.C, ... });
KeyCapture.FilterKeys = true;

By default, KeyCapture is initialized with a list of only the basic 'typing keys': A-Z, 0-9, space, punctuation, navigation (up, down, left, right, home, end, page up, page down), and editing (backspace, delete) keys. This disables any special key combinations: ALT+F4, CTL+F4, Windows keys, and so on. It also disables any hot keys built-into the keyboard, such as volume up/down, quick launch buttons, and so on.

If you're using KeyCapture in a Windows Forms application, you can enable FilterKeys when the main form loads and disable it when the main form is destroyed. The Dispose method can be found in the Form1.designer.cs file.

/// <span class="code-SummaryComment"><summary>
</span>/// Initializes the form.
/// <span class="code-SummaryComment"></summary>
</span>private void Form1_Load(object sender, EventArgs e)
{
    // Disable unwanted keys.
    KeyCapture.FilterKeys = true;
}

/// <span class="code-SummaryComment"><summary>
</span>/// Clean up any resources being used and re-enables normal key events.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="disposing">true if managed resources
</span>///     should be disposed; otherwise, false.<span class="code-SummaryComment"></param>
</span>protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
      components.Dispose();
    }

    // Re-enable normal key events.
    KeyCapture.FilterKeys = false;

    base.Dispose(disposing);
}

Points of interest

KDBLLHOOKSTRUCT contains details passed to our callback method in the form of a struct. Here, we're mainly interested in the Keys field.

/// <span class="code-SummaryComment"><summary>
</span>/// Information about the low-level keyboard input event.
/// <span class="code-SummaryComment"></summary>
</span>[StructLayout(LayoutKind.Sequential)]
private struct KBDLLHOOKSTRUCT
{
    public Keys key;
    public int scanCode;
    public int flags;
    public int time;
    public IntPtr extra;
}

This constant identifies the type of event that we are adding a 'hook' (or callback) to process. This is the constant that identifies the low-level keyboard event.

private const int WH_KEYBOARD_LL = 13;

This delegate is the callback signature required to receive low-level keyboard events:

// System level function used to hook and unhook keyboard input
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

These are the Win32 function signatures that we are importing from user32.dll for the purpose of receiving low-level keyboard events:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int id, 
        LowLevelKeyboardProc callback, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hook);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hook, 
                      int nCode, IntPtr wp, IntPtr lp);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string name);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern short GetAsyncKeyState(Keys key);

When the FilterKeys property is set to true, this code registers our callback method CaptureKey as a hook:

// When enabled, register our key capture method.
if (value)
{
    // Get Current Module
    ProcessModule objCurrentModule = Process.GetCurrentProcess().MainModule;

    // Assign callback function each time keyboard process
    objKeyboardProcess = new LowLevelKeyboardProc(CaptureKey);

    // Setting Hook of Keyboard Process for current module
    ptrHook = SetWindowsHookEx(WH_KEYBOARD_LL, objKeyboardProcess, 
                  GetModuleHandle(objCurrentModule.ModuleName), 0);
}

When the FilterKeys property is set to false, this code removes our callback and enables keyboard events to be processed normally:

if (ptrHook != IntPtr.Zero)
{
    UnhookWindowsHookEx(ptrHook);
    ptrHook = IntPtr.Zero;
}

CaptureKey is the callback method that receives low-level keyboard events. We convert the lp parameter into a KBDLLHOOKSTRUCT struct that allows us to extract the type of key that's been pressed. If we decide to reject the key, we return (IntPtr)1 to indicate that we've 'handled' the key already. If not (or if nCode is a negative number), we pass the keyboard event along to the next hook in the chain by calling CallNextHookEx.

/// <span class="code-SummaryComment"><summary>
</span>/// Capture keystrokes and filter which key events are permitted to continue.
/// <span class="code-SummaryComment"></summary>
</span>private static IntPtr CaptureKey(int nCode, IntPtr wp, IntPtr lp)
{
    // If the nCode is non-negative, filter the key stroke.
    if (nCode >

History

This is the first iteration of KeyCapture and of this article; created on February 27, 2010.

License

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

Share

About the Author

AC Everspaugh

United States United States
No Biography provided

Comments and Discussions

 
Generalutility to record mouse movement and keyboard PinmemberMember 1096512422-Jul-14 22:39 
Generalthis is useful PinmemberBenDerPan8-Oct-13 22:29 
GeneralMy vote of 3 PinmemberKA123451-Apr-11 11:21 
GeneralNice Article Pinmembericetea948-Mar-11 0:33 
Very nice article!
 
I'm using Windows 7 and noticed that the Windows+L is not blocked by the keyboard hook. I don't now how this behaves in other versions of Windows, but i thought that it's worth to mention.
GeneralMy vote of 5 PinmemberNeoPunk6-Mar-11 7:26 
GeneralNo ! Pinmemberherves4-Mar-11 3:59 
GeneralRe: No ! PinmemberNeoPunk6-Mar-11 7:26 
GeneralRe: No ! [modified] Pinmemberherves6-Mar-11 23:16 
GeneralRe: No ! PinmemberNeoPunk7-Mar-11 1:51 
GeneralRe: No ! Pinmemberherves8-Mar-11 0:09 
GeneralMy vote of 3 PinmemberEdMan1964-Mar-11 3:11 
QuestionWhy write something thats already in the framework??? PinmemberPaw Jershauge3-Mar-11 23:15 
AnswerRe: Why write something thats already in the framework??? PinmemberPaw Jershauge3-Mar-11 23:48 
GeneralRe: Why write something thats already in the framework??? PinmemberNeoPunk6-Mar-11 12:35 
GeneralRe: Why write something thats already in the framework??? PinmemberPaw Jershauge8-Mar-11 22:03 
GeneralRe: Why write something thats already in the framework??? PinmemberBenDerPan8-Oct-13 22:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 3 Mar 2011
Article Copyright 2011 by AC Everspaugh
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid