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
{
public class KeyCapture
{
[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;
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>
public static List<Keys> PermittedKeys = new List<Keys>(
new 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,
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,
Keys.Delete, Keys.Back,
Keys.LShiftKey, Keys.RShiftKey, Keys.Shift, Keys.ShiftKey,
Keys.Up, Keys.Down, Keys.Left, Keys.Right, Keys.PageUp, Keys.PageDown,
Keys.Home, Keys.End,
});
private static IntPtr CaptureKey(int nCode, IntPtr wp, IntPtr lp)
{
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.
private void Form1_Load(object sender, EventArgs e)
{
KeyCapture.FilterKeys = true;
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
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.
[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:
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:
if (value)
{
ProcessModule objCurrentModule = Process.GetCurrentProcess().MainModule;
objKeyboardProcess = new LowLevelKeyboardProc(CaptureKey);
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
.
private static IntPtr CaptureKey(int nCode, IntPtr wp, IntPtr lp)
{
if (nCode >= 0)
{
KBDLLHOOKSTRUCT KeyInfo =
(KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lp, typeof(KBDLLHOOKSTRUCT));
if (! PermittedKeys.Contains(KeyInfo.key))
return (IntPtr)1;
}
return CallNextHookEx(ptrHook, nCode, wp, lp);
}
History
This is the first iteration of KeyCapture
and of this article; created on February 27, 2010.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.