C# 使用低级键盘挂钩检查自定义热键/击键

C# 使用低级键盘挂钩检查自定义热键/击键,c#,enums,bit-manipulation,keyboard-hook,C#,Enums,Bit Manipulation,Keyboard Hook,我将非常简单地解释,因为我的另一篇文章似乎不是很清楚 我正在开发一个应用程序,您可以添加一个自定义热键并触发uppon,它会做一些事情(没关系)。你可以从头顶上说“RegisterHotKey”,但不幸的是,这对我一点帮助都没有。User32 dll的RegisterHotKey不允许将多个非修改键放入一个热键,例如: RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT, K_A); // Shift + A: ok! Registe

我将非常简单地解释,因为我的另一篇文章似乎不是很清楚

我正在开发一个应用程序,您可以添加一个自定义热键并触发uppon,它会做一些事情(没关系)。你可以从头顶上说“RegisterHotKey”,但不幸的是,这对我一点帮助都没有。User32 dll的RegisterHotKey不允许将多个非修改键放入一个热键,例如:

RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT, K_A); // Shift + A: ok!
RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT | MOD_ALT, K_A); // Shift + Alt + A: 2 modifiers, ok!
RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT, K_A | K_B); // Shift + A + B: not possible!
还有一些其他系统热键不能被“覆盖”,但情况也并非如此

为此,我创建了一个C++ DLL来钩住键盘并使用SnNoDeMyEdEx()调用将数据发送到我的C语言窗体应用程序。数据以我表格的WndProc完美地发送和接收

击键类如下所示:

using System.Collections.Generic;
using System.Windows.Input;

// Usage: Keystroke k = new Keystroke(Key.LShift, Key.A, Key.B)
public class Keystroke
{
    public List<Key> Keys { get; private set; };

    public Keystroke(params Key[] keys)
    {
        Keys = new List<Key>(keys);
    }
}
使用System.Collections.Generic;
使用System.Windows.Input;
//用法:击键k=新击键(Key.LShift,Key.A,Key.B)
公共类击键
{
公共列表密钥{get;private set;};
公共击键(参数键[]键)
{
密钥=新列表(密钥);
}
}
正如我们所看到的,这是一个非常简单的片段。基本上,热键/击键可以由任意组合键组成,甚至仅由修改键组成(例如Shift+Alt)

键盘事件按以下方式在my WndProc中调度:

public class MainForm : Form
{
    List<Keystroke> customHotkeys;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == LowLevelKeyboardHook.Message)
        {
            WinAPI.KBDLLHOOKSTRUCT keyboardHook = (WinAPI.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(WinAPI.KBDLLHOOKSTRUCT));

            switch (m.WParam.ToInt32())
            {
                case WinAPI.WM_KEYDOWN:
                case WinAPI.WM_SYSKEYDOWN:
                    DispatchKeyDownMessage(keyboardHook);
                    break;
                case WinAPI.WM_KEYUP:
                case WinAPI.WM_SYSKEYUP:
                    // ...
                    break;
            }
        }

        base.WndProc(ref m);
    }

    private void DispatchKeyDownMessage(WinAPI.KBDLLHOOKSTRUCT keyboardHook)
    {
        // Convert the key code into a System.Windows.Input.Key enum value
        Key key = KeyInterop.KeyFromVirtualKey(keyboardHook.vkCode);

        foreach (Keystroke keystroke in customHotkeys)
        {
            if (keystroke.Keys.Contains(key))
            {
                Console.WriteLine("Woot! Some hotkey wraps this key.");
            }
        }
    }
}
公共类主窗体:窗体
{
列出自定义热键;
受保护的覆盖无效WndProc(参考消息m)
{
if(m.Msg==LowLevelKeyboardHook.Message)
{
WinAPI.KBDLLHOOKSTRUCT keyboardHook=(WinAPI.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(m.LParam,typeof(WinAPI.KBDLLHOOKSTRUCT));
开关(m.WParam.ToInt32())
{
案例WinAPI.WM_KEYDOWN:
案例WinAPI.WM_SYSKEYDOWN:
DispatchKeyDownMessage(键盘挂钩);
打破
案例WinAPI.WM_KEYUP:
案例WinAPI.WM\u SYSKEYUP:
// ...
打破
}
}
基准WndProc(参考m);
}
私有void DispatchKeyDownMessage(WinAPI.KBDLLHOOKSTRUCT keyboardHook)
{
//将密钥代码转换为System.Windows.Input.key枚举值
Key=KeyInterop.KeyFromVirtualKey(keyboardHook.vkCode);
foreach(自定义热键中的击键)
{
if(keystroke.Keys.Contains(key))
{
WriteLine(“呜!一些热键包住了这个键。”);
}
}
}
}
上述方法确实有效,但这似乎没有最好的性能。因为我们处理的是全局系统钩子,所以必须非常快速地发送消息。为此,我考虑在击键中添加一些位标志技术,这样就不再需要“keystroke.Keys.Contains(key)”了,从而加快了速度。像这样的东西:

public class Keystroke
{
    public List<Key> Keys { get; private set; } 
    public Key Flags { get; private set; }

    public Keystroke(params Key[] keys)
    {
        Keys = new List<Key>(keys);

        foreach (Key key in keys)
        {
            Flags |= key;
        }
    }
}

// Usage: bool containsK = (someKeystroke.Flags & Key.W) == Key.W;
公共类击键
{
公共列表密钥{get;private set;}
公钥标志{get;private set;}
公共击键(参数键[]键)
{
密钥=新列表(密钥);
foreach(钥匙插入钥匙)
{
标志|=键;
}
}
}
//用法:boolcontainsk=(someKeystroke.Flags&Key.W)=Key.W;
我们已经知道,System.Windows.Input.Key枚举没有[Flags]属性。但是,我可以改为使用System.Windows.Forms.Keys枚举,但正如文档中所述,我们不能将这些值用于组合按位操作,因为它们不是互斥的:

为了解决标志问题,我创建了以下类:

public class Flagger<T> where T : struct
{
    private static Dictionary<int, ulong> dictionary = new Dictionary<int, ulong>();

    static Flagger()
    {
        int indexer = 0;

        // Since values can be duplicated, we use names instead
        foreach (String name in Enum.GetNames(typeof(T)))
        {
            ulong value = 1UL << indexer++; // 0, 1, 2, 4, 8, 16...

            Console.WriteLine("{0} key now corresponds to the value {1}", name, value);

            dictionary.Add(name.GetHashCode(), value); 
        }
    }

    private ulong flags;

    public void Add(T value)
    {
        // Create hash only once for both checkup and storation
        int hash = value.ToString().GetHashCode();

        if (Check(hash))
        {
            ulong flag = dictionary[hash];

            flags &= flag;
        }
    }

    public void Remove(T value)
    {
        // Create hash only once for both checkup and storation
        int hash = value.ToString().GetHashCode();

        if (Check(hash))
        {
            ulong flag = dictionary[hash];

            flags &= ~flag;
        }            
    }

    /// <summary>
    /// Tests whether a value has already been added or not
    /// </summary>
    public bool Check(T value)
    {
        int hash = value.ToString().GetHashCode();

        return Check(hash);
    }

    /// <summary>
    /// Quick checkup because no hash needs to be computed
    /// </summary>
    private bool Check(int hash)
    {
        if (dictionary.ContainsKey(hash))
        {
            ulong flag = dictionary[hash];

            return (flags & flag) == flag;
        }

        return false;
    }
}
公共类标记器,其中T:struct
{
私有静态字典Dictionary=新字典();
静态标记器()
{
int索引器=0;
//因为值可以复制,所以我们使用名称
foreach(Enum.GetNames(typeof(T))中的字符串名)
{

ULUN值=1UL C*A.C= C++。语言不是同义词,只是因为它们都是从同一个字母开始的。这里的标签有特定的含义,不应该只是因为它们听起来很熟悉而随意添加。只添加与你的问题实际相关的标签。如果你不确定,请阅读标签说明。如果你还不确定,不要。添加标记;如果需要,这里会有人为您添加标记。谢谢。@KenWhite我知道它们是不同的语言。我不确定您是否花时间阅读了整篇文章或仅仅是标记,但我确实指定了它们的使用位置(C++用于dll,C#用于表单应用程序,如您所见)如果你的意思是我的问题完全与C语言无关,而不是C++(因为DLL的工作做得相当好),那么我必须告诉你C++中的解决方案也可以完成(如果有人想出了一个解决方案)。你的问题要么是关于应用程序中的代码,要么是关于DLL中的代码;它们都不是同时用两种语言编写的,是吗?如果不是,那么只有一个标记适用,因为你的问题是关于该语言中的代码。因此,一个语言标记适用于这个问题。是的,我读过了。除了说明DLL是w之外在C++中,与问题无关的C++——甚至你说DLL工作得完美无瑕。@ PID是什么“和弦”?为了吸引键盘,已经使用了一个C++ DLL。就像我说的,你不能登记击键(RealSturtKekey调用),例如“Shift +A+B+C”(多个非修改键)。。应用程序应该允许用户创建包含任意键组合的击键(即使仅由修改键组成:例如Shift+Alt)。如果您知道一个C#函数,该函数允许您使用多个非修改键创建全局热键(“Shift+a+B+C”示例),请让我知道!我不是pid,但和弦是多个键的组合(想象一架钢琴,多个键在弹奏)
for (int i = 0; i < 200; i++)
{
    Console.WriteLine("{0} generated {1}", i, 1UL << i);
}