Filtering Special Keys Using C#






4.50/5 (12 votes)
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
{
/// <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
.
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.
/// <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.
/// <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.
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
.
/// <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.