【问题标题】:Respond to multiple KeyDown events响应多个 KeyDown 事件
【发布时间】:2013-05-25 00:34:21
【问题描述】:

我正在制作一个简单的 WinForm 赛车游戏。我有两个对象——汽车,当按下键时它们在表单上移动(Form1KeyDown_Event)。

唯一的问题是,当一个玩家按下一个键时,另一个玩家不能按下他的键(什么都没有发生)。但是当第一个玩家松开钥匙时,第二个玩家可以按下他的一个键并正常控制他的车。

如何同时收听两个播放器键?我应该使用线程并让每辆车都有自己的线程吗?

【问题讨论】:

  • 我没有给你解决方案,但我可以告诉你,多线程不是答案。无论如何,所有 KeyDown 事件都在主 (UI) 线程上处理。
  • 这可能是键盘的硬件限制。当您在键入文档时按一个键同时按另一个键是否有效?
  • 您是否尝试过使用 keydown 和 keyup 代替,并“记住”哪些键处于关闭状态?
  • 您可以使用标准的 KeyDown() 事件,也可以使用其中的 GetKeyState() API 来测试您感兴趣的所有键。

标签: c# winforms input multiple-instances


【解决方案1】:

这是一个简单的示例,说明您可以使用 keyup 和 keydown 事件同时收听多个键。

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace WinFormTest {
    public partial class Form1 : Form {
        private readonly IDictionary<Keys, bool> downState;

        public Form1() {
            InitializeComponent();
            downState = new Dictionary<Keys, bool>();
            downState.Add(Keys.W, false);
            downState.Add(Keys.D, false);

            KeyDown += remember;
            KeyUp += forget;
        }

        protected override void OnLoad(EventArgs e) {
            base.OnLoad(e);
            Timer timer = new Timer() { Interval = 100 };
            timer.Tick += updateGUI;
            timer.Start();
        }

        private void remember(object sender, KeyEventArgs e) {
            downState[e.KeyCode] = true;
        }

        private void forget(object sender, KeyEventArgs e) {
            downState[e.KeyCode] = false;
        }

        private void updateGUI(object sender, EventArgs e) {
            label1.Text = downState[Keys.W] ? "Forward" : "-";
            label2.Text = downState[Keys.D] ? "Right" : "-";
        }
    }
}

【讨论】:

    【解决方案2】:

    您可能想要调查到较低级别并使用 Windows 挂钩来检测键盘事件。这需要 P/Invoking 到本机方法,但非常简单。你想要的钩子是 WH_LL_KEYBOARD。详情请见pinvoke.net

    您需要一些样板,但它与您可以合理预期的键盘事件一样接近:

    [StructLayout(LayoutKind.Sequential)]
    public struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    
    public delegate IntPtr LowLevelKeyboardProc(int, IntPtr, KBDLLHOOKSTRUCT);
    
    [DllImport("kernel32.dll")]
    public static extern uint GetCurrentThreadId();
    
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);
    
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetWindowsHookEx(int idhook, LowLevelKeyboardProc proc, IntPtr hMod, uint threadId);
    
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
    
    public static IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        using (var curProc = Process.GetCurrentProcess())
        using (var curMod = curProc.MainModule)
        {
            return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curMod.ModuleName), 0u);
        }
    }
    
    public IntPtr MyKeyboardHook(int code, IntPtr wParam, ref KBDLLHOOKSTRUCT keyboardInfo)
    {
        if (code < 0)
        {
            return CallNextHookEx(IntPtr.Zero, wParam, ref keyboardInfo);
        }
    
        // Do your thing with the keyboard info.
    
        return CallNextHookEx(IntPtr.Zero, code, wParam, ref keyboardInfo);
    }
    

    确保在您的应用不再需要处理程序时取消挂钩。 KBDLLHOOKSTRUCT 封装了 Windows 将为您提供的有关键盘事件的所有信息;其成员的详细信息可以是found at MSDN

    这种钩子的一个细节是它会在注册它的线程上执行,所以一定要注意这一点,如果它会做任何长时间运行的事情,不要在 UI 线程上设置它.

    【讨论】:

    • 这与使用按键不同吗?从您的示例中我不明白 OP 如何使用它来查看是否按下了某个键,您介意解释一下吗?
    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-05
    • 1970-01-01
    相关资源
    最近更新 更多