简体   繁体   中英

Console check for pressed keys like in monogame

In my console application, I would like to be able to detect for key presses somewhat like in mono, where you can get a list of the currently pressed keys and check if a key is on the list, or detect if a key is being pressed. My problem with using

if( Console.KeyAvailable ) k = Console.ReadKey( true ).Key;

is the fact that there will be a delay after I press the first key. You can see an example of this if you open notepad and hold down "A". A will be pressed, then a delay, and then A will be spammed.

How can I get keyboard input without the delay in between presses? I'm not afraid to use low level functions such as hooking into kernel32.dll

So I wrote some code, based off what I've read so far.

Step 1: Copy the following code into your console application. It must be STAThread or it will throw an error. Put in the commands you want to use in the switch statement. All other keys will be blocked by ReadKey(true).

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using System;
using System.Windows.Forms;
using System.Windows.Input;

namespace ConsoleApplication10
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            KeyListener.RegisterHotKey(Keys.A);
            KeyListener.HotKeyPressed += new EventHandler<HotKeyEventArgs>(KeyListener_HotKeyPressed);
            while (true)
            {
                Console.ReadKey(true);
            }
        }

        static void KeyListener_HotKeyPressed(object sender, HotKeyEventArgs e)
        {
            switch (e.Key)
            {
                case Keys.A:
                {
                    Console.WriteLine("Do stuff");
                    return;
                }
                default:
                    return;
            }
        }
    }
}

Step 2: Add a reference to System.Windows.Forms. You need this to have a hidden Form that is necessary for the message loop for the keyboard hook.

Step 3: Add the following static class. It does all the heavy lifting of the keyboard hooks for you, so you don't have to do it.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleApplication10
{
    public static class KeyListener
    {
        public static event EventHandler<HotKeyEventArgs> HotKeyPressed;

        public static int RegisterHotKey(Keys key, KeyModifiers modifiers)
        {
            _windowReadyEvent.WaitOne();
            int id = System.Threading.Interlocked.Increment(ref _id);
            _wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)modifiers, (uint)key);
            return id;
        }

        public static int RegisterHotKey(Keys key)
        {
            _windowReadyEvent.WaitOne();
            int id = System.Threading.Interlocked.Increment(ref _id);
            _wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)KeyModifiers.None, (uint)key);
            return id;
        }

        public static void UnregisterHotKey(int id)
        {
            _wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
        }

        delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
        delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);

        private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
        {
            RegisterHotKey(hwnd, id, modifiers, key);
        }

        private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
        {
            UnregisterHotKey(_hwnd, id);
        }

        private static void OnHotKeyPressed(HotKeyEventArgs e)
        {
            if (KeyListener.HotKeyPressed != null)
            {
                KeyListener.HotKeyPressed(null, e);
            }
        }

        private static volatile MessageWindow _wnd;
        private static volatile IntPtr _hwnd;
        private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
        static KeyListener()
        {
            Thread messageLoop = new Thread(delegate ()
            {
                Application.Run(new MessageWindow());
            });
            messageLoop.Name = "MessageLoopThread";
            messageLoop.IsBackground = true;
            messageLoop.Start();
        }

        private class MessageWindow : Form
        {
            public MessageWindow()
            {
                _wnd = this;
                _hwnd = this.Handle;
                _windowReadyEvent.Set();
            }

            protected override void WndProc(ref Message m)
            {
                if (m.Msg == WM_HOTKEY)
                {
                    HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
                    KeyListener.OnHotKeyPressed(e);
                }

                base.WndProc(ref m);
            }

            protected override void SetVisibleCore(bool value)
            {
                // Ensure the window never becomes visible
                base.SetVisibleCore(false);
            }

            private const int WM_HOTKEY = 0x312;
        }

        [DllImport("user32", SetLastError = true)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

        [DllImport("user32", SetLastError = true)]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        private static int _id = 0;
    }


    public class HotKeyEventArgs : EventArgs
    {
        public readonly Keys Key;
        public readonly KeyModifiers Modifiers;

        public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
        {
            this.Key = key;
            this.Modifiers = modifiers;
        }

        public HotKeyEventArgs(IntPtr hotKeyParam)
        {
            uint param = (uint)hotKeyParam.ToInt64();
            Key = (Keys)((param & 0xffff0000) >> 16);
            Modifiers = (KeyModifiers)(param & 0x0000ffff);
        }
    }

    [Flags]
    public enum KeyModifiers
    {
        None = 0,
        Alt = 1,
        Control = 2,
        Shift = 4,
        Windows = 8,
        NoRepeat = 0x4000
    }
}

Step 4:

Now, there's still a delay. It's way more elegant, but you still have the operating system fighting you. So what to do?

You have two options.

a) You implement a timing option and you simply repeat the action for as long as a key is down on the timer tick event. You can either copy the code or merge it with the hotkey approach that I've given you.

See here for details: Removing the delay after KeyDown event?

private bool _moveUp;
private bool _moveDown;
private bool _moveLeft;
private bool _moveRight;

// You can add the Timer in the Winforms Designer instead if you like;
// The Interval property can be configured there at the same time, along
// with the Tick event handler, simplifying the non-Designer code here.
private System.Windows.Forms.Timer _movementTimer = new Timer { Interval = 100 };

public MainForm()
{
    InitializeComponent();

    _movementTimer.Tick += movementTimer_Tick;
}

private void movementTimer_Tick(object sender, EventArgs e)
{
    _DoMovement();
}

private void _DoMovement()
{
    if (_moveLeft) Player.MoveLeft();
    if (_moveRight) Player.MoveRight();
    if (_moveUp) Player.MoveUp();
    if (_moveDown) Player.MoveDown();
}

// You could of course override the OnKeyDown() method instead,
// assuming the handler is in the Form subclass generating the
// the event.
public void MainForm_KeyDown(object sender, KeyEventArgs e)
{
    if (e.IsRepeat)
    {
        // Ignore key repeats...let the timer handle that
        return;
    }

    switch (e.KeyCode)
    {
    case Keys.Up:
        _moveUp = true;
        break;
    case Keys.Down:
        _moveDown = true;
        break;
    case Keys.Left:
        _moveLeft = true;
        break;
    case Keys.Right:
        _moveRight = true;
        break;
    }

    _DoMovement();
    _movementTimer.Start();
}

public void MainForm_KeyUp(object sender, KeyEventArgs e)
{
    switch (e.KeyCode)
    {
    case Keys.Up:
        _moveUp = false;
        break;
    case Keys.Down:
        _moveDown = false;
        break;
    case Keys.Left:
        _moveLeft = false;
        break;
    case Keys.Right:
        _moveRight = false;
        break;
    }

    if (!(_moveUp || _moveDown || _moveLeft || _moveRight))
    {
        _movementTimer.Stop();
    }
}

b) In your Main method, you get the delay setting, set the delay programmatically to the lowest setting, and on Application Exit, you set it back to its original setting.

See here for where to find it in the registry: https://superuser.com/questions/388160/keyboard-repeat-rate-repeat-delay-values-in-win7

And how to read/write to the registry: https://msdn.microsoft.com/en-us/library/microsoft.win32.registry_methods(v=vs.110).aspx

Note: With this approach, there's still a small delay. It's small, but it's there. Good luck.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM