Как правильно создать глобальную горячую клавишу мыши в wpf C#

Я пытаюсь создать глобальные горячие клавиши мыши в моей программе, используя SetWindowsHookEx(), До сих пор я пытался создать их как в одном посте, который я видел здесь, но я действительно не знаю, как это закончить. Проблема в настоящее время в том, что я точно не знаю, что такое _globalMouseHookCallback.

Это то, что я написал до сих пор:

class GlobalHotkey
{

    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, uint threadId);

    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);

    [DllImport("user32.dll")]
    static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

    internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    private IntPtr _hGlobalMouseHook;


    MainWindow _m;

    private const int WH_KEYBOARD_LL = 13;
    private const int WH_MOUSE_LL = 14;

    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;
    private const int WM_RBUTTONDOWN = 0x0204;
    private const int WM_RBUTTONUP = 0x0205;

    private static IntPtr hook = IntPtr.Zero;

    public GlobalHotkey(MainWindow m)
    {
        _m = m;
    }

    public void SetUpHook()
    {
        _m.rtbLog.AppendText("Setting up global Hotkey \n");

        _globalMouseHookCallback = LowLevelMouseProc;

        _hGlobalMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _globalMouseHookCallback, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);

        if (_hGlobalMouseHook == IntPtr.Zero)
        {
            _m.rtbLog.AppendText("Unable to set up global mouse hook\n");
        }
    }

    public void ClearHook()
    {
        _m.rtbLog.AppendText("Deleting global mouse hook\n");

        if (_hGlobalMouseHook != IntPtr.Zero)
        {
            if (!UnhookWindowsHookEx(_hGlobalMouseHook))
            {
                _m.rtbLog.AppendText("Unable to delete global mouse hook\n");
            }

            _hGlobalMouseHook = IntPtr.Zero;
        }

    }

    public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0)
        {
            var wmMouse = wParam;

            if (wmMouse == (IntPtr)WM_LBUTTONDOWN)
            {
                _m.rtbLog.AppendText("Right Mouse down");
            }
            if (wmMouse == (IntPtr)WM_LBUTTONUP)
            {
                _m.rtbLog.AppendText("Left Mouse up");
            }

            if (wmMouse == (IntPtr)WM_RBUTTONDOWN)
            {
                _m.rtbLog.AppendText("Right Mouse down");
            }
            if (wmMouse == (IntPtr)WM_RBUTTONUP)
            {
                _m.rtbLog.AppendText("Right Mouse up");
            }
        }

        return CallNextHookEx(_hGlobalMouseHook, nCode, wParam, lParam);
    }
}

Эта глобальная горячая клавиша довольно сложна, это как учебник, который легко объясняет для таких новичков, как я:P?

РЕДАКТИРОВАТЬ Итак, я попытался адаптировать пример Koby Ducks для моего кода.

Это мой класс Hotkey:

class GlobalHotkey
{

    MainWindow _m;

    private static readonly object sSyncObj = new object();
    private static readonly HashSet<Key> sDownKeys = new HashSet<Key>();
    private static readonly Dictionary<Key, Action> sPressActions = new Dictionary<Key, Action>();
    private static readonly Dictionary<Key, Action> sReleaseActions = new Dictionary<Key, Action>();

    public GlobalHotkey(MainWindow m)
    {
        _m = m;

    }

    public static void ProcessKeyDown(KeyEventArgs args)
    {
        var key = args.Key;
        var action = default(Action);
        lock (sSyncObj)
        {
            if (!sDownKeys.Contains(key))
            {
                sDownKeys.Add(key);

                if (sPressActions.TryGetValue(key, out action))
                {
                    args.Handled = true;
                }
            }
        }

        action.Invoke();
    }

    public static void ProcessKeyUp(KeyEventArgs args)
    {
        var key = args.Key;
        var action = default(Action);
        lock (sSyncObj)
        {
            if (sDownKeys.Remove(key))
            {
                if (sReleaseActions.TryGetValue(key, out action))
                {
                    args.Handled = true;
                }
            }
        }

        action.Invoke();
    }

    public static void AttachPressAction(Key key, Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        lock (sSyncObj)
        {
            sPressActions.Add(key, action);
        }
    }

    public static bool DetachPressAction(Key key)
    {
        lock (sSyncObj)
        {
            return sPressActions.Remove(key);
        }
    }

    public static void AttachReleaseAction(Key key, Action action)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        lock (sSyncObj)
        {
            sReleaseActions.Add(key, action);
        }
    }

    public static bool DetachReleaseAction(Key key)
    {
        lock (sSyncObj)
        {
            return sReleaseActions.Remove(key);
        }
    }
}

И я создал свое действие

public void MyTestAction()
    {
        rtbLog.AppendText("The B key was pressed");
    }
myAction = new Action(MyTestAction);

Но как только я добавил свои обработчики событий в PreviewKeyUp- и Down Event, он выдал ошибку, в которой говорилось, что параметры ProcessKeyUp- и Down не совпадают с PreviewKeyUp- и Down.

PreviewKeyDown += GlobalHotkey.ProcessKeyDown;
PreviewKeyUp += GlobalHotkey.ProcessKeyUp;

1 ответ

Решение

Редактировать: Для ввода независимо от текущего окна (также называемого "глобальным"), смотрите этот ответ в API клавиатуры Win32.

Для ввода, когда ваше приложение сфокусировано (или "локально"), вы можете использовать события предварительного просмотра.

public static class HotKeySystem
{
    public static void ProcessKeyDown(object sender, KeyEventArgs args)
    {
        var key = args.Key;
        var action = default(Action);
        lock (sSyncObj) {
            if (!sDownKeys.Contains(key)) {
                sDownKeys.Add(key);
                if (sPressActions.TryGetValue(key, out action)) {
                    args.Handled = true;
                }
            }
        }
        // Invoke outside of the lock.
        action?.Invoke();
    }
    public static void ProcessKeyUp(object sender, KeyEventArgs args)
    {
        var key = args.Key;
        var action = default(Action);
        lock (sSyncObj) {
            if (sDownKeys.Remove(key)) {
                if (sReleaseActions.TryGetValue(key, out action)) {
                    args.Handled = true;
                }
            }
        }
        // Invoke outside of the lock.
        action?.Invoke();
    }
    public static void AttachPressAction(KeyCode key, Action action)
    {
        if (action == null) {
            throw new ArgumentNullException(nameof(action));
        }
        lock (sSyncObj) {
            sPressActions.Add(key, action);
        }
    }
    public static bool DetachPressAction(KeyCode key)
    {
        lock (sSyncObj) {
            return sPressActions.Remove(key);
        }
    }
    public static void AttachReleaseAction(KeyCode key, Action action)
    {
        if (action == null) {
            throw new ArgumentNullException(nameof(action));
        }
        lock (sSyncObj) {
            sReleaseActions.Add(key, action);
        }
    }
    public static bool DetachReleaseAction(KeyCode key)
    {
        lock (sSyncObj) {
            return sReleaseActions.Remove(key);
        }
    }

    private static readonly object sSyncObj = new object();
    // The keys that are currently down.
    private static readonly HashSet<KeyCode> sDownKeys = new HashSet<KeyCode>();
    // Actions triggered when a key was up, but is now down.
    private static readonly Dictionary<KeyCode, Action> sPressActions = new Dictionary<KeyCode, Action>();
    // Actions triggered when a key was down, but is now up.
    private static readonly Dictionary<KeyCode, Action> sReleaseActions = new Dictionary<KeyCode, Action>();
}

// When possible, subclass your windows from this to automatically add hotkey support.
public class HotKeyWindow : Window
{
    protected override void OnPreviewKeyDown(KeyEventArgs args)
    {
        HotKeySystem.ProcessKeyDown(this, args);
        base.OnPreviewKeyDown(args);
    }
    protected override void OnPreviewKeyUp(KeyEventArgs args)
    {
        HotKeySystem.ProcessKeyUp(this, args);
        base.OnPreviewKeyUp(args);
    }
}

// When not possible, attach event handlers like this:
window.PreviewKeyDown += HotKeySystem.ProcessKeyDown;
window.PreviewKeyUp += HotKeySystem.ProcessKeyUp;

// Use it like this:
HotKeySystem.AttachPressAction(KeyCode.F1, () => {
    // F1 hotkey functionality.
});

Независимо от того, используете ли вы этот метод или Win32 API, учтите последствия. Если у вас есть граница "А", вы не сможете ввести "А" или "А" в элементы управления вводом текста. Один из способов обойти это:

public static void ProcessKeyDown(object sender, KeyEventArgs args)
{
    // Detect keyboard input controls you may have issues with.
    // If one has keyboard focus, skip hotkey processing.
    if (Keyboard.FocusedElement is TextBox) {
        return;
    }

    // ...
}
Другие вопросы по тегам