Global event monitoring with WPF

50 Views Asked by At

I would like to ask for help in this matter: in WPF, I would like to monitor the keydown, mousedown and mousewheel events even if the window is not currently active.I use c# with WPF. I tried many things that I found on the Internet, but none of them worked, let's say there is not really any material on this topic. The last one I tried for the following:

namespace GlobalInputEvents
{
    public partial class MainWindow : Window
    {
        private LowLevelKeyboardListener _keyboardListener;

        public MainWindow()
        {
            InitializeComponent();

            _keyboardListener = new LowLevelKeyboardListener();
            _keyboardListener.OnKeyPressed += GlobalKeyDownHandler;
            _keyboardListener.HookKeyboard();

            this.Loaded += (sender, e) =>
            {
                // Az egér események kezelése
                this.PreviewMouseDown += GlobalMouseDownHandler;
                this.PreviewMouseWheel += GlobalMouseWheelHandler;
            };
        }

        private void GlobalKeyDownHandler(object sender, KeyEventArgs e)
        {
            MessageBox.Show("A billentyűt megnyomták a háttérben is! Billentyű: " + e.Key);
        }

        private void GlobalMouseDownHandler(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Az egérgombot lenyomták a háttérben is! Gomb: " + e.ChangedButton);
        }

        private void GlobalMouseWheelHandler(object sender, MouseWheelEventArgs e)
        {
            MessageBox.Show("Az egér görgőjét használták a háttérben is! Görgetés: " + e.Delta);
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            _keyboardListener.UnHookKeyboard();
        }
    }
}

namespace GlobalInputEvents
{
    public class LowLevelKeyboardListener
    {
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;

        private readonly LowLevelKeyboardProc _proc;
        private IntPtr _hookID = IntPtr.Zero;

        public event EventHandler<KeyEventArgs> OnKeyPressed;

        public LowLevelKeyboardListener()
        {
            _proc = HookCallback;
        }

        public void HookKeyboard()
        {
            _hookID = SetHook(_proc);
        }

        public void UnHookKeyboard()
        {
            UnhookWindowsHookEx(_hookID);
        }

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

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                OnKeyPressed?.Invoke(this, new KeyEventArgs(KeyInterop.KeyFromVirtualKey(vkCode)));
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr 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);
    }
}
1

There are 1 best solutions below

0
rusden220 On

You did everything right, but you get the window handle wrong, In WPF there is a class that gives the window pointer to handle OS events: WindowInteropHelper So to solve the problem you need to

  1. Get a pointer to the wpf window new WindowInteropHelper(this).Handle before calling the HookKeyboard in the MainWindow constructor
  2. Add a parameter IntPtr hwnd in the HookKeyboard method
  3. Call SetWindowsHookEx with hwnd from step 2
  4. remove
using (var curProcess = System.Diagnostics.Process.GetCurrentProcess())
using (var curModule = curProcess.MainModule)