Использование глобальной клавиатуры (WH_KEYBOARD_LL) в WPF / C # - PullRequest
56 голосов
/ 28 октября 2009

Я соединил воедино из кода, который нашел в интернете сам WH_KEYBOARD_LL класс помощника:

Поместите следующий код в некоторые из ваших библиотек утилит, пусть это будет YourUtils.cs :

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace MYCOMPANYHERE.WPF.KeyboardHelper
{
    public class KeyboardListener : IDisposable
    {
        private static IntPtr hookId = IntPtr.Zero;

        [MethodImpl(MethodImplOptions.NoInlining)]
        private IntPtr HookCallback(
            int nCode, IntPtr wParam, IntPtr lParam)
        {
            try
            {
                return HookCallbackInner(nCode, wParam, lParam);
            }
            catch
            {
                Console.WriteLine("There was some error somewhere...");
            }
            return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
        }

        private IntPtr HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
                {
                    int vkCode = Marshal.ReadInt32(lParam);

                    if (KeyDown != null)
                        KeyDown(this, new RawKeyEventArgs(vkCode, false));
                }
                else if (wParam == (IntPtr)InterceptKeys.WM_KEYUP)
                {
                    int vkCode = Marshal.ReadInt32(lParam);

                    if (KeyUp != null)
                        KeyUp(this, new RawKeyEventArgs(vkCode, false));
                }
            }
            return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
        }

        public event RawKeyEventHandler KeyDown;
        public event RawKeyEventHandler KeyUp;

        public KeyboardListener()
        {
            hookId = InterceptKeys.SetHook((InterceptKeys.LowLevelKeyboardProc)HookCallback);
        }

        ~KeyboardListener()
        {
            Dispose();
        }

        #region IDisposable Members

        public void Dispose()
        {
            InterceptKeys.UnhookWindowsHookEx(hookId);
        }

        #endregion
    }

    internal static class InterceptKeys
    {
        public delegate IntPtr LowLevelKeyboardProc(
            int nCode, IntPtr wParam, IntPtr lParam);

        public static int WH_KEYBOARD_LL = 13;
        public static int WM_KEYDOWN = 0x0100;
        public static int WM_KEYUP = 0x0101;

        public 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);
            }
        }

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

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

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

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

    public class RawKeyEventArgs : EventArgs
    {
        public int VKCode;
        public Key Key;
        public bool IsSysKey;

        public RawKeyEventArgs(int VKCode, bool isSysKey)
        {
            this.VKCode = VKCode;
            this.IsSysKey = isSysKey;
            this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
        }
    }

    public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args);
}

Который я использую так:

App.xaml

<Application ...
    Startup="Application_Startup"
    Exit="Application_Exit">
    ...

App.xaml.cs

public partial class App : Application
{
    KeyboardListener KListener = new KeyboardListener();

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
    }

    void KListener_KeyDown(object sender, RawKeyEventArgs args)
    {
        Console.WriteLine(args.Key.ToString());
        // I tried writing the data in file here also, to make sure the problem is not in Console.WriteLine
    }

    private void Application_Exit(object sender, ExitEventArgs e)
    {
        KListener.Dispose();
    }
}

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

Воспроизвести эту проблему довольно просто, нажимайте на клавиши как сумасшедший, обычно за окном.

Я подозреваю, что есть какая-то злая проблема с многопоточностью , у кого-нибудь есть идеи, как сохранить эту работу?


Что я уже пробовал:

  1. Замена return HookCallbackInner(nCode, wParam, lParam); чем-то простым.
  2. Замена на асинхронный вызов, попытка поставить Sleep 5000ms (и т. Д.).

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

Ответы [ 5 ]

18 голосов
/ 30 октября 2009

Вы создаете свой делегат обратного вызова, встроенный в вызов метода SetHook. Этот делегат в конечном итоге будет собирать мусор, поскольку вы нигде не будете на него ссылаться. И после того, как делегат будет собран мусором, вы не получите больше обратных вызовов.

Чтобы предотвратить это, вам нужно поддерживать ссылку на делегат в активном состоянии до тех пор, пока хук находится на своем месте (до тех пор, пока вы не вызовете UnhookWindowsHookEx).

2 голосов
/ 30 мая 2012

Победитель: Захват ввода с клавиатуры в WPF , который предлагает сделать:

TextCompositionManager.AddTextInputHandler(this,
    new TextCompositionEventHandler(OnTextComposition));

... а затем просто используйте свойство Text аргумента обработчика события:

private void OnTextComposition(object sender, TextCompositionEventArgs e)
{
    string key = e.Text;
    ...
}
1 голос
/ 28 октября 2009

IIRC, при использовании глобальных перехватов, если ваша DLL не возвращается из обратного вызова достаточно быстро, вы удаляетесь из цепочки обратных вызовов.

Так что, если вы говорите, что он работает некоторое время, но если вы набираете слишком быстро, он перестает работать, я мог бы предложить просто сохранить ключи в некотором месте в памяти и затем сбросить их. Например, вы можете проверить источник для некоторых клавиатурных шпионов, так как они используют эту же технику.

Хотя это может не решить вашу проблему напрямую, это должно как минимум исключить одну возможность.

Вы думали об использовании GetAsyncKeyState вместо глобальной ловушки для регистрации нажатий клавиш? Для вашего приложения этого может быть достаточно, есть много полностью реализованных примеров, и его было проще реализовать.

0 голосов
/ 31 мая 2017

Я использовал метод Дилана для перехвата глобального ключевого слова в приложении WPF и обновления перехвата после каждого нажатия клавиши, чтобы предотвратить остановку событий после нескольких щелчков. ИДК, если это хорошая или плохая практика, но она выполняет свою работу.

      _listener.UnHookKeyboard();
      _listener.HookKeyboard();

Детали реализации здесь

0 голосов
/ 07 сентября 2015

Я действительно искал это. Спасибо, что разместили это здесь.
Теперь, когда я проверял ваш код, я обнаружил несколько ошибок. Код не работал сначала. И он не мог справиться с двумя кнопками, т. Е. CTRL + P .
То, что я изменил, эти значения выглядят ниже:
private void HookCallbackInner до

private void HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
                {
                    int vkCode = Marshal.ReadInt32(lParam);

                    if (KeyDown != null)
                        KeyDown(this, new RawKeyEventArgs(vkCode, false));
                }
            }
        }

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using FileManagerLibrary.Objects;

namespace FileCommandManager
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        readonly KeyboardListener _kListener = new KeyboardListener();
        private DispatcherTimer tm;

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            _kListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
        }

        private List<Key> _keysPressedIntowSecound = new List<Key>();
        private void TmBind()
        {
            tm = new DispatcherTimer();
            tm.Interval = new TimeSpan(0, 0, 2);
            tm.IsEnabled = true;
            tm.Tick += delegate(object sender, EventArgs args)
            {
                tm.Stop();
                tm.IsEnabled = false;
                _keysPressedIntowSecound = new List<Key>();
            };
            tm.Start();
        }

        void KListener_KeyDown(object sender, RawKeyEventArgs args)
        {
            var text = args.Key.ToString();
            var m = args;
            _keysPressedIntowSecound.Add(args.Key);
            if (tm == null || !tm.IsEnabled)
                TmBind();
        }

        private void Application_Exit(object sender, ExitEventArgs e)
        {
            _kListener.Dispose();
        }
    }
}

этот код работает на 100% в Windows 10 для меня :) Я надеюсь, что это поможет вам

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...