响应多个KeyDown事件

3
我正在制作一个简单的WinForm赛车游戏。我有两个对象 - 赛车,当按下键时(Form1KeyDown_Event),它们在表单上移动。
唯一的问题是,当一个玩家按下键时,另一个玩家不能按他的键(什么也不会发生)。但是当第一个玩家释放键时,第二个玩家可以按下自己的键并正常控制他的车。
如何同时监听两个玩家的按键?我应该使用线程并让每辆车都在自己的线程上吗?

1
我无法为您提供解决方案,但我可以告诉您多线程不是答案。所有的KeyDown事件都在主(UI)线程上处理。 - Jan Dörrenhaus
1
这可能是键盘的硬件限制。在您打字时,如果您按下一个键同时按下另一个键,它是否起作用? - Ilya Kogan
你尝试过使用keydown和keyup,然后“记住”哪些键处于按下状态吗? - Patrick
你可以使用标准的KeyDown()事件,但也可以在其中使用GetKeyState() API来测试你感兴趣的所有按键。 - Idle_Mind
2个回答

3

这里有一个简单的例子,可以通过使用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" : "-";
        }
    }
}

0

你可能想要调查一下降低级别并使用Windows钩子来检测键盘事件。这需要P/Invoke到本地方法,但相当简单。你需要的钩子是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将向您提供有关键盘事件的所有信息;其成员的详细信息可以在MSDN上找到。

这种类型的钩子的一个细节是它在注册它的线程上执行,因此请确保注意这一点,并且如果它要执行任何长时间运行的操作,请不要在UI线程上设置它。


这与使用keypressed有何不同?从你的例子中,我不明白OP如何使用它来查看是否按下了键,您能解释一下吗? - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接