none
Отлов событий нажатия и отпускания функциональных клавиш. RRS feed

  • Вопрос

  • Пишу небольшую софтинку - "Удобняшку". Примерный алгоритм работы следующий: 

    По нажатию на клавишу CTRL рядом с курсором мыши появляется менюшка, если незадолго до этого (5-10 секунд) совершалась операция копирования текста, появляется другая менюшка. Назначение этих менюшек весьма тривиальна - заливка содержимого буфера обмена на удаленный сервер и получение информации обратно. Подобная программа для меня бесценна т.к. мне необходимо что бы всегда под рукой были ссылки и фрагменты текста/кода необходимые мне для работы и творчества. При этом не хотелось бы совершать лишних телодвижений что бы сохранять/получать информацию. Вот и родилась подобная идея. Собственно все вопросы я решил за исключением одного. Все примеры кода забрасывающие хук на клавиатуру которые я опробовал, выдают ошибку когда я пытаюсь отлавливать нажатие одного лишь CTRL. Ошибка "что то там с обратным вызовом". При запуске в режиме дебага, при нескольких нажатиях студия выводит предупреждение что что то там вызывает само себя и программа из за этого может работать нестабильно. Знающие люди, кому не сложно, напишите работоспособный код который выполняет всего лишь одну функцию - начинает в программе события - "клавиша CTRL нажата" и "клавиша CTRL отпущенна". Можно без событий, просто коментарием указать где мне "вклиниваться". Единственное требование к коду - стабильность.

    ТТХ:  .NET FW 4.0, 4.5, C#, WPF, VS2012.
    • Изменено JusteG 27 февраля 2013 г. 8:00
    27 февраля 2013 г. 6:38

Ответы

  • Готово. Вот класс заточенный под топик. Извеняюсь что без комментариев, но я обычно не комментирую фрагменты кода где все прозрачно. Собстно вотъ™:

    public static class InterceptKeys
    {
        public enum KeyState
        {
            KeyDown,
            KeyUp
        }
    
        public static KeyState GetState(Keys Key)
        {
            if (_KeysDown.Contains(Key))
                return KeyState.KeyDown;
            else
                return KeyState.KeyUp;
        }
    
        public delegate void KeyboardEvent(Keys Key);
        public delegate void KeyboardStateEvent(Keys Key, InterceptKeys.KeyState State);
    
        public static event KeyboardEvent KeyDown;
        public static event KeyboardEvent KeyUp;
        public static event KeyboardStateEvent KeyEvent;
    
        private static List<Keys> _KeysDown = new List<Keys>();
        public static List<Keys> KeysDown
        {
            get
            {
                return _KeysDown;
            }
        }
    
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;
    
        public static void Hook()
        {
            _hookID = SetHook(_proc);
        }
    
        public static void UnHook()
        {
            UnhookWindowsHookEx(_hookID);
        }
    
        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)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    if (!_KeysDown.Contains((Keys)vkCode))
                    {
                        if (KeyDown != null)
                            KeyDown((Keys)vkCode);
    
                        if (KeyEvent != null)
                            KeyEvent((Keys)vkCode, KeyState.KeyDown);
    
                        _KeysDown.Add((Keys)vkCode);
                    }
                }
                if (wParam == (IntPtr)WM_KEYUP)
                {
                    if (KeyUp != null)
                        KeyUp((Keys)vkCode);
    
                    if (KeyEvent != null)
                        KeyEvent((Keys)vkCode, KeyState.KeyUp);
    
                    _KeysDown.Remove((Keys)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);
    }

    Использование:

    public static void Main()
    {
        InterceptKeys.Hook();                               //Вешаем хук.
        //InterceptKeys.KeyDown += InterceptKeys_KeyDown;     //Подписываемся на событие "Клавиша нажата".
        //InterceptKeys.KeyUp += InterceptKeys_KeyUp;         //Подписываемся на событие "Клавиша отпущена".
        InterceptKeys.KeyEvent += InterceptKeys_KeyEvent;   //Подписываемся на универсальное событие изменения состояния клавиши
                                                            //Это событие заменяет предыдущие 2 и использование его вместе с ними
                                                            //в данном примере (Вообще?) не целесообразно.
    
        //List<Keys> PressedKeys = InterceptKeys.KeysDown;    //Получаем список зажатых в настоящий момент клавиш.
        //InterceptKeys.KeyState IsCtrlPressed =
        //    InterceptKeys.GetState(Keys.ControlKey);        //Получаем состояние клавиши CTRL
    
        Application.Run();
        InterceptKeys.UnHook();                             //Снимаем хук.
    }
    
    static void InterceptKeys_KeyEvent(Keys Key, InterceptKeys.KeyState State)
    {
        switch (State)
        {
            case InterceptKeys.KeyState.KeyDown:
                Console.WriteLine("Key Down: " + Key);
                break;
            case InterceptKeys.KeyState.KeyUp:
                Console.WriteLine("Key Up: " + Key);
                break;
        }
    }
    
    static void InterceptKeys_KeyUp(Keys Key)
    {
        Console.WriteLine("Key Up: " + Key);
    }
    
    static void InterceptKeys_KeyDown(Keys Key)
    {
        Console.WriteLine("Key Down: " + Key);
    }

    Скачать готовое решение для NFW 4.5 можно здесь:  Исходник, Экзэшник.

    Еще раз большое спасибо Кирилу. "+"

    • Предложено в качестве ответа Kirill Bessonov 27 февраля 2013 г. 12:04
    • Помечено в качестве ответа JusteG 27 февраля 2013 г. 12:05
    • Изменено JusteG 27 февраля 2013 г. 12:11
    27 февраля 2013 г. 12:00
  • Low-Level Keyboard Hook in C# - Отличный рабочий пример.
    • Помечено в качестве ответа JusteG 27 февраля 2013 г. 10:49
    27 февраля 2013 г. 9:26

Все ответы

  • Укажите, какие технологии вы используете, а то так вам долго будут отвечать.
    27 февраля 2013 г. 7:01
  • Да, точно, извеняюсь. 

    .NET FW 4.0, 4.5, C#, WPF, VS2012.

    • Изменено JusteG 27 февраля 2013 г. 7:08
    27 февраля 2013 г. 7:07
  • Вам нужен глобальный хук или же вы отлавливаете только в своем приложении?
    27 февраля 2013 г. 8:11
  • Глобальный я пологаю, т.к. в "дежурном состоянии" моя программа не имеет активных окон, только иконка в трее. Мало-мальский UI появляется при нажатии клавиши CTRL и скрывается при ее отпускании. Работать, как я уже указывал в топике, я планирую не со своим приложением. Оно - лишь инструмент-помошник для упрощения работы с другими приложениями, и следовательно "отлавливать" нажатый CTRL мне необходимо и в браузере, и на рабочем столе, и студии... Если это можно решить локальным хоткеем моего приложения тогда можно решить вопрос и так. Но я склоняюсь к тому, что исходя из решаемой задачи, мне необходим именно глобальный хук.
    27 февраля 2013 г. 8:33
  • Да, еще, я думаю немаловажное уточнение.

    Мне необходимо не перехватить нажатие клавиши CTRL, те я не собираюсь "отбирать" это событие у Windows. Мне необходимо что бы моя прогамма была "проинформированна" о нажатии/отпускании этой кнопки, а она в свою очередь выполняла тот функционал на который собственно и расчитана. Просто я использовал какой-то из примеров. Пока моя программа работала, в системе не работал контрол, или залипал "по настроению".

    • Изменено JusteG 27 февраля 2013 г. 9:03
    27 февраля 2013 г. 9:01
  • Low-Level Keyboard Hook in C# - Отличный рабочий пример.
    • Помечено в качестве ответа JusteG 27 февраля 2013 г. 10:49
    27 февраля 2013 г. 9:26
  • Спасибо.

    Пример действительно наглядный и рабочий, когда модернизирую класс под описание вопроса в топике выложу его тут.

    27 февраля 2013 г. 10:49
  • Ждем! :)
    27 февраля 2013 г. 10:51
  • Готово. Вот класс заточенный под топик. Извеняюсь что без комментариев, но я обычно не комментирую фрагменты кода где все прозрачно. Собстно вотъ™:

    public static class InterceptKeys
    {
        public enum KeyState
        {
            KeyDown,
            KeyUp
        }
    
        public static KeyState GetState(Keys Key)
        {
            if (_KeysDown.Contains(Key))
                return KeyState.KeyDown;
            else
                return KeyState.KeyUp;
        }
    
        public delegate void KeyboardEvent(Keys Key);
        public delegate void KeyboardStateEvent(Keys Key, InterceptKeys.KeyState State);
    
        public static event KeyboardEvent KeyDown;
        public static event KeyboardEvent KeyUp;
        public static event KeyboardStateEvent KeyEvent;
    
        private static List<Keys> _KeysDown = new List<Keys>();
        public static List<Keys> KeysDown
        {
            get
            {
                return _KeysDown;
            }
        }
    
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;
    
        public static void Hook()
        {
            _hookID = SetHook(_proc);
        }
    
        public static void UnHook()
        {
            UnhookWindowsHookEx(_hookID);
        }
    
        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)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    if (!_KeysDown.Contains((Keys)vkCode))
                    {
                        if (KeyDown != null)
                            KeyDown((Keys)vkCode);
    
                        if (KeyEvent != null)
                            KeyEvent((Keys)vkCode, KeyState.KeyDown);
    
                        _KeysDown.Add((Keys)vkCode);
                    }
                }
                if (wParam == (IntPtr)WM_KEYUP)
                {
                    if (KeyUp != null)
                        KeyUp((Keys)vkCode);
    
                    if (KeyEvent != null)
                        KeyEvent((Keys)vkCode, KeyState.KeyUp);
    
                    _KeysDown.Remove((Keys)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);
    }

    Использование:

    public static void Main()
    {
        InterceptKeys.Hook();                               //Вешаем хук.
        //InterceptKeys.KeyDown += InterceptKeys_KeyDown;     //Подписываемся на событие "Клавиша нажата".
        //InterceptKeys.KeyUp += InterceptKeys_KeyUp;         //Подписываемся на событие "Клавиша отпущена".
        InterceptKeys.KeyEvent += InterceptKeys_KeyEvent;   //Подписываемся на универсальное событие изменения состояния клавиши
                                                            //Это событие заменяет предыдущие 2 и использование его вместе с ними
                                                            //в данном примере (Вообще?) не целесообразно.
    
        //List<Keys> PressedKeys = InterceptKeys.KeysDown;    //Получаем список зажатых в настоящий момент клавиш.
        //InterceptKeys.KeyState IsCtrlPressed =
        //    InterceptKeys.GetState(Keys.ControlKey);        //Получаем состояние клавиши CTRL
    
        Application.Run();
        InterceptKeys.UnHook();                             //Снимаем хук.
    }
    
    static void InterceptKeys_KeyEvent(Keys Key, InterceptKeys.KeyState State)
    {
        switch (State)
        {
            case InterceptKeys.KeyState.KeyDown:
                Console.WriteLine("Key Down: " + Key);
                break;
            case InterceptKeys.KeyState.KeyUp:
                Console.WriteLine("Key Up: " + Key);
                break;
        }
    }
    
    static void InterceptKeys_KeyUp(Keys Key)
    {
        Console.WriteLine("Key Up: " + Key);
    }
    
    static void InterceptKeys_KeyDown(Keys Key)
    {
        Console.WriteLine("Key Down: " + Key);
    }

    Скачать готовое решение для NFW 4.5 можно здесь:  Исходник, Экзэшник.

    Еще раз большое спасибо Кирилу. "+"

    • Предложено в качестве ответа Kirill Bessonov 27 февраля 2013 г. 12:04
    • Помечено в качестве ответа JusteG 27 февраля 2013 г. 12:05
    • Изменено JusteG 27 февраля 2013 г. 12:11
    27 февраля 2013 г. 12:00