Click here to Skip to main content
15,878,748 members
Articles / Programming Languages / C#
Article

Filtering Special Keys Using C#

Rate me:
Please Sign up or sign in to vote.
4.50/5 (13 votes)
3 Mar 2011CPOL3 min read 69.5K   36   15
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

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

namespace WindowsKeyCapture
{
    /// <summary>
    /// Allows filtering of any keys, including special
    /// keys like CTRL, ALT, and Windows keys,
    /// Win32 windows hooks.
    /// </summary>
    /// <remarks>
    /// Original code example from:
    /// http://geekswithblogs.net/aghausman/archive/
    ///     2009/04/26/disable-special-keys-in-win-app-c.aspx
    /// on 27-FEB-2010
    /// </remarks>
    public class KeyCapture
    {
      /// <summary>
      /// Information about the low-level keyboard input event.
      /// </summary>
      [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;

      /// <summary>
      /// 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.

C#
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.

C#
/// <summary>
/// Initializes the form.
/// </summary>
private void Form1_Load(object sender, EventArgs e)
{
    // Disable unwanted keys.
    KeyCapture.FilterKeys = true;
}

/// <summary>
/// Clean up any resources being used and re-enables normal key events.
/// </summary>
/// <param name="disposing">true if managed resources
///     should be disposed; otherwise, false.</param>
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.

C#
/// <summary>
/// Information about the low-level keyboard input event.
/// </summary>
[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.

C#
private const int WH_KEYBOARD_LL = 13;

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

C#
// 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:

C#
[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:

C#
// 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:

C#
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.

C#
/// <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 >= 0)
    {
      KBDLLHOOKSTRUCT KeyInfo = 
        (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lp, typeof(KBDLLHOOKSTRUCT));

      // Reject any key that's not on our list.
      if (! PermittedKeys.Contains(KeyInfo.key))
        return (IntPtr)1;
    }

    // Pass the event to the next hook in the chain.
    return CallNextHookEx(ptrHook, nCode, wp, lp);
}

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)


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

Comments and Discussions

 
Generalthis is useful Pin
BenDerPan8-Oct-13 21:29
BenDerPan8-Oct-13 21:29 
GeneralMy vote of 3 Pin
KA123451-Apr-11 10:21
KA123451-Apr-11 10:21 
GeneralNice Article Pin
fatho17-Mar-11 23:33
fatho17-Mar-11 23:33 
GeneralMy vote of 5 Pin
NeoPunk6-Mar-11 6:26
NeoPunk6-Mar-11 6:26 
GeneralNo ! Pin
herves4-Mar-11 2:59
herves4-Mar-11 2:59 
GeneralRe: No ! Pin
NeoPunk6-Mar-11 6:26
NeoPunk6-Mar-11 6:26 
GeneralRe: No ! [modified] Pin
herves6-Mar-11 22:16
herves6-Mar-11 22:16 
GeneralRe: No ! Pin
NeoPunk7-Mar-11 0:51
NeoPunk7-Mar-11 0:51 
GeneralRe: No ! Pin
herves7-Mar-11 23:09
herves7-Mar-11 23:09 
GeneralMy vote of 3 Pin
Ed Nutting4-Mar-11 2:11
Ed Nutting4-Mar-11 2:11 
QuestionWhy write something thats already in the framework??? Pin
Paw Jershauge3-Mar-11 22:15
Paw Jershauge3-Mar-11 22:15 
AnswerRe: Why write something thats already in the framework??? Pin
Paw Jershauge3-Mar-11 22:48
Paw Jershauge3-Mar-11 22:48 
GeneralRe: Why write something thats already in the framework??? Pin
NeoPunk6-Mar-11 11:35
NeoPunk6-Mar-11 11:35 
GeneralRe: Why write something thats already in the framework??? Pin
Paw Jershauge8-Mar-11 21:03
Paw Jershauge8-Mar-11 21:03 
GeneralRe: Why write something thats already in the framework??? Pin
BenDerPan8-Oct-13 21:37
BenDerPan8-Oct-13 21:37 

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

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