Реагировать на несколько событий KeyDown

Я делаю простую гоночную игру WinForm. У меня есть два объекта - автомобили, и они перемещаются по форме при нажатии клавиши (Form1KeyDown_Event).

Единственное, что когда один игрок нажимает клавишу, другой игрок не может нажать свою клавишу (ничего не происходит). Но когда первый игрок отпускает ключ, второй игрок может нажать одну из своих клавиш и нормально управлять своей машиной.

Как я могу прослушивать две клавиши плеера одновременно? Должен ли я использовать потоки и иметь каждый автомобиль в своем потоке?


person Joudicek Jouda    schedule 25.05.2013    source источник
comment
У меня нет решения для вас, но я могу сказать вам, что многопоточность НЕ является ответом. Все события KeyDown в любом случае обрабатываются в основном (UI) потоке.   -  person Jan Dörrenhaus    schedule 25.05.2013
comment
Возможно, это аппаратное ограничение клавиатуры. Это работает, когда вы нажимаете одну клавишу, а другую нажимаете, когда вы печатаете документ?   -  person Ilya Kogan    schedule 25.05.2013
comment
Пробовали ли вы вместо этого использовать keydown и keyup и помнить, какие клавиши находятся в нажатом состоянии?   -  person Patrick    schedule 25.05.2013
comment
Вы можете использовать стандартное событие KeyDown(), но также использовать ссылку GetKeyState() внутри него для проверки всех интересующих вас ключей.   -  person Idle_Mind    schedule 25.05.2013


Ответы (2)


Вот простой пример того, что вы можете сделать, чтобы прослушивать несколько клавиш одновременно, используя вместо этого события 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" : "-";
        }
    }
}
person Patrick    schedule 25.05.2013

Возможно, вы захотите исследовать переход на более низкий уровень и использование перехватчиков 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 предоставит вам о событии клавиатуры; сведения о его членах можно найти в MSDN.

Одной из особенностей этого типа хука является то, что он выполняется в потоке, который его зарегистрировал, поэтому убедитесь, что вы приняли это к сведению, и не устанавливайте его в потоке пользовательского интерфейса, если он собирается делать что-то длительное.

person Ben    schedule 25.05.2013
comment
Отличается ли это от использования нажатия клавиш? Я не понимаю из вашего примера, как OP может использовать его, чтобы увидеть, нажата ли клавиша или нет, не могли бы вы объяснить это? - person Patrick; 25.05.2013