3

I have a C# windowed application running and I want to close it when I press ESC from anywhere, even when my application does not have focus. How can I implement this?

I found some hook up keyboard which is Low Level Control I have no idea and don't understand.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
Jongz Puangput
  • 5,527
  • 10
  • 58
  • 96
  • I know how to hook up keyboard when it focus but I want to know how to hook when it not focus.. thx for your respond – Jongz Puangput Sep 07 '14 at 17:06
  • If you want to register a global hotkey, see here: http://stackoverflow.com/questions/48935/how-can-i-register-a-global-hot-key-to-say-ctrlshiftletter-using-wpf-and-ne – Lukas S. Sep 07 '14 at 17:07
  • 1
    If you need globally active hotkeys, [`RegisterHotKey`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms646309.aspx) is the way to go. Don't use keyboard hooks. But strongly recommend not using ESC as a global hotkey since it's an important local hotkey in most applications. – CodesInChaos Sep 07 '14 at 17:08
  • you mean that if I RegisterHotKey other application with not be able to use it right? – Jongz Puangput Sep 07 '14 at 17:10
  • You can look into global low-level hook instead of using hotkeys. Look into the differences and choose which is more appropriate. – SimpleVar Sep 07 '14 at 17:11
  • I see.. actually I don't understand how to use low level with my window application that much – Jongz Puangput Sep 07 '14 at 17:12
  • You should not steal Esc from any other application. At the very least make sure that Esc is still sent to the focused application so that it also works as intended. Why do you want Esc anywhere to close your application? This is not normal, are you sure your users will understand this? – Lasse V. Karlsen Sep 07 '14 at 17:13
  • my application It work like macro so, user may want to stop it if they set somethings wrong – Jongz Puangput Sep 07 '14 at 17:15

3 Answers3

7

Use this class:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace myNameSpace
{
    class InterceptKeys
    {
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_ALTDOWN = 0x0104;
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;

        public static void Start()
        {
            _hookID = SetHook(_proc);
        }

        public static void Stop()
        {
            UnhookWindowsHookEx(_hookID);
        }

        public static event KeyEventHandler OnKeyDown;

        private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        private delegate IntPtr LowLevelKeyboardProc(
            int nCode, IntPtr wParam, IntPtr lParam);

        private static IntPtr HookCallback(
            int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_ALTDOWN))
            {
                var vkCode = (Keys)Marshal.ReadInt32(lParam);
                OnKeyDown(null, new KeyEventArgs(vkCode));
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

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

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

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

In this way:

myNameSpace.InterceptKeys.OnKeyDown+= new KeyEventHandler(myKeyDown);

myNameSpace.InterceptKeys.Start();

that onKeyDown can be like this:

void myKeyDown(object sender, KeyEventArgs e)
{
    // Some code for closing you form
    // or any thing you need after press Esc
    // with e.KeyCode
};
Nabi
  • 304
  • 2
  • 8
0

if you want you can set global hook with http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C

Noam Wies
  • 54
  • 4
0

For those looking for a complete solution to detect global key presses and also make key presses, I've figured it all out and put the code in pastebin.com to "never" be deleted. Note especially InterceptKeys.cs here (do a Ctrl+F):

http://pastebin.com/u7FUhgYr

^ This code is complete, but it's sub-optimal.


Then I improved the code a bit (one change is the new SendKeys function I created which sends whatever key presses/releases you want - much easier to use!). This class here, MakroKeys:

http://pastebin.com/Dedx6hRw

has two static functions that can be used to convert from string to System.Windows.Forms.Keys and vice versa (which would be useful if you want the user to be able to input their own key via text and/or display the currently pressed key via text); and this has the improved MainForm.cs code to go with it (containing the SendKeys function):

http://pastebin.com/BXzmWeMK

(Note the MakroKeys.GetKey("lcontrolkey") call, which demonstrates the usage of that functionality.)


With all of this combined you can do some pretty amazing things. Note also that because this is importing DLL's and calling Windows API functions, you can do this rather easily in e.g. c++ too. You're welcome, world. ;)

Andrew
  • 5,839
  • 1
  • 51
  • 72