控制台检查按下的键,就像在 monogame 中一样
Posted
技术标签:
【中文标题】控制台检查按下的键,就像在 monogame 中一样【英文标题】:Console check for pressed keys like in monogame 【发布时间】:2016-12-13 17:14:26 【问题描述】:在我的控制台应用程序中,我希望能够检测按键,有点像单声道,您可以在其中获取当前按下的按键的列表并检查列表中是否有按键,或者检测是否有按键正在按下。我的使用问题
if( Console.KeyAvailable ) k = Console.ReadKey( true ).Key;
是我按下第一个键后会有延迟。如果您打开记事本并按住“A”,您可以看到这样的示例。 A 将被按下,然后延迟,然后 A 将被发送垃圾邮件。
如何在两次按下之间没有延迟的情况下获得键盘输入?我不怕使用低级函数,例如挂钩 kernel32.dll
【问题讨论】:
你试过简单的谷歌搜索下面的C# Listen for key press in .NET console app
是的,但是我得到的大部分结果都与 Console.ReadKey 相关,它会执行“A”、暂停,然后在我希望它只是垃圾邮件时发送垃圾邮件“A” A”一按就可以了。我确实找到了一个结果,blogs.msdn.microsoft.com/toub/2006/05/03/…,但我不确定如何运行我的代码。
对于反对者和评论者,他说他不希望在第一次按键后出现延迟。就像这样: - .... -------------。这是一个关于控制台和 ReadKey 本质的有效问题。 @MethodMan,他的代码已经完成了“监听按键”的建议。无论它是否是一个紧密的循环,都不能否定存在延迟的事实,这是 Windows 操作系统这样做的。话虽如此,OP,Console 不支持这样的事情。您将需要从挂钩中获取按键事件。但是,有很多很棒的库/Nuget 包可以做到这一点。
我正在写这个。我将为您编写一些示例代码,因为这种方法有些细微差别。答案将在几个小时后出现,因为现在是午餐时间。
所以我写了一些拦截消息循环的代码,但最终还是由操作系统控制延迟。消息本身是延迟的,因此无法简单地重复消息。这里真正的方法是要么实现一个不稳定的计时系统(这是许多人的方法),要么只要一个键按下就继续触发一个事件,直到有一个键向上事件。当我回家并得到答案时,我会继续处理这个问题,因为在 Stack Overflow 上关于它的内容很少或非常糟糕。对于 Google 员工来说,它可能值得单独发布。
【参考方案1】:
所以我根据我目前阅读的内容编写了一些代码。
第 1 步:将以下代码复制到您的控制台应用程序中。它必须是 STAThread 否则会抛出错误。在 switch 语句中输入要使用的命令。 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;
第 2 步:添加对 System.Windows.Forms 的引用。您需要它有一个隐藏的表单,这是键盘钩子的消息循环所必需的。
第 3 步:添加以下静态类。它会为您完成所有繁重的键盘挂钩工作,因此您不必这样做。
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
第 4 步:
现在,还有延迟。它更加优雅,但您仍然有操作系统与您抗衡。那么该怎么办呢?
你有两个选择。
a) 您实现了一个计时选项,只要在计时器滴答事件上按下按键,您就只需重复该操作。您可以复制代码或将其与我提供的热键方法合并。
详情请看这里: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)在您的 Main 方法中,您获取延迟设置,以编程方式将延迟设置为最低设置,然后在应用程序退出时将其设置回其原始设置。
请参阅此处了解在注册表中的位置:https://superuser.com/questions/388160/keyboard-repeat-rate-repeat-delay-values-in-win7
以及如何读/写注册表:https://msdn.microsoft.com/en-us/library/microsoft.win32.registry_methods(v=vs.110).aspx
注意:使用这种方法,仍然会有一点延迟。它很小,但它就在那里。祝你好运。
【讨论】:
复杂,但值得。以上是关于控制台检查按下的键,就像在 monogame 中一样的主要内容,如果未能解决你的问题,请参考以下文章