在控制台C#中检测单个按键的按下

4

我刚开始学编程,决定从C#开始。我打算写一个简单的控制台程序来检测按键,如果只按下回车键,它就会显示数字。问题是,你可以一直按住键盘不放,它将继续显示数字。我应该在我的代码中添加什么,才能使程序仅检测单次按下并忽略用户是否长按键?(我没有在CONSOLEC#中找到有关此问题的任何内容,只有对Forms的解释。这个论坛和网络上都没有)

提前感谢您。

static void Main(string[] args)
{
    Console.WriteLine("Press Enter to play!");

    int num = 0;
    void WaitForKey(ConsoleKey key)
    {
        while (Console.ReadKey(true).Key != key)
        { }
    }

    for (int i = 0; i < 10; i++)
    {
        WaitForKey(ConsoleKey.Enter);
        Console.Write("{0} ", num);
        num++;
    }
}

4
@L.B 实际上,C# 7.0 引入了本地函数。 https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/ - nbokmans
1
这不是问题,而是一种“特性”。你的程序已经比它本来可以运行的慢了数百万倍,增加一个数字只需要几纳秒。但对于人眼来说,键盘重复的速度足够快,看起来瞬间完成。即使它需要100毫秒,对于处理器周期来说也是一个漫长的时代。你需要重新考虑这个问题,从代码片段中我们无法确定你想要实现什么。不要使用Thread.Sleep()来让它变得更慢。 - Hans Passant
@HansPassant 那不是代码片段,那是整个程序。我已经清楚地描述了我的意图:我希望程序只检测单个按键并忽略用户按住键的情况。 - YRSHKHN
如果这就是整个程序,那么你没有问题,任何用户都会很快厌倦它并再次删除它。隐藏真正的意图从来不是一个好主意,没有人能提出有用的替代方案。按原样,你必须能够获得KeyUp事件的等效项,你无法从控制台获取它。它旨在使程序中的用户交互非常简单,这也是故意的。特性而非错误。 - Hans Passant
@HansPassant 我知道这对用户来说并不是很有趣,它不应该是娱乐性的,因为我是为了个人目的而制作的,我绝不会隐藏我的真实意图。根据您的评论,我理解在控制台中无法做到我想要的,只能在表单中实现? - YRSHKHN
显示剩余3条评论
4个回答

1
如果您的任务是计算键入/按住键的更改次数,您只需记住上一个键即可。
var lastChar = char.MaxValue;
var index = 0;
do
{
    var x = Console.ReadKey();
    if (lastChar != x.KeyChar)
    {
        lastChar = x.KeyChar;
        Console.WriteLine(++index);
    }
} while (index < 10);

如果您需要定义单个键,可以使用 StopWatch 类来检查自上一个键输入以来经过的时间(在示例中,300 仅用于测试,建议您深入研究以确定是否符合您的目标)。
var sw = Stopwatch.StartNew();
do
{
    var x = Console.ReadKey();
    if (sw.ElapsedMilliseconds > 300)
    {
        Console.WriteLine(++index);
    }
    sw.Restart();
} while (index < 10);

或者将两种方式结合起来

1

是的,在这里你不能使用ReadKey。我建议使用ReadConsoleInput WinApi函数。你可以编写包装器类来实现这个目的,例如:

internal class KeyboardInput
{
    private readonly short _exitKey;
    private readonly uint[] _keyStates = new uint[short.MaxValue]; 

    public KeyboardInput(ConsoleKey exitKey)
    {
        _exitKey = (short) exitKey;

        // subscribe with empty delegates to prevent null reference check before call
        OnKeyDown += delegate { };
        OnKeyUp += delegate { };
    }

    public event Action<char, short> OnKeyDown;
    public event Action<char, short> OnKeyUp;

    public void Run()
    {
        var exitKeyPressed = false;
        var nRead = 0;
        var records = new INPUT_RECORD[10];

        var handle = GetStdHandle(STD_INPUT_HANDLE);
        while (!exitKeyPressed)
        {
            ReadConsoleInputW(handle, records, records.Length, ref nRead);

            for (var i = 0; i < nRead; i++)
            {
                // process only Key events
                if (records[i].EventType != KEY_EVENT) continue;

                // process key state
                ProcessKey(records[i].KeyEvent.wVirtualKeyCode, records[i].KeyEvent.bKeyDown,
                    records[i].KeyEvent.UnicodeChar);

                // check for exit key press
                if ((exitKeyPressed = records[i].KeyEvent.wVirtualKeyCode == _exitKey) == true) break;
            }
        }
    }

    private void ProcessKey(short virtualKeyCode, uint keyState, char key)
    {
        if (_keyStates[virtualKeyCode] != keyState)
            if (keyState == 1) OnKeyDown(key, virtualKeyCode);
            else OnKeyUp(key, virtualKeyCode);

        _keyStates[virtualKeyCode] = keyState;
    }

    #region Native methods

    private const short KEY_EVENT = 0x0001;
    private const int STD_INPUT_HANDLE = -10;

    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool ReadConsoleInputW(IntPtr hConsoleInput, [Out] INPUT_RECORD[] lpBuffer, int nLength,
        ref int lpNumberOfEventsRead);

    [StructLayout(LayoutKind.Explicit)]
    private struct INPUT_RECORD
    {
        [FieldOffset(0)] public readonly short EventType;

        //union {
        [FieldOffset(4)] public KEY_EVENT_RECORD KeyEvent;

        //[FieldOffset(4)]
        //public MOUSE_EVENT_RECORD MouseEvent;
        //[FieldOffset(4)]
        //public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
        //[FieldOffset(4)]
        //public MENU_EVENT_RECORD MenuEvent;
        //[FieldOffset(4)]
        //public FOCUS_EVENT_RECORD FocusEvent;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct KEY_EVENT_RECORD
    {
        public readonly uint bKeyDown;
        public readonly short wRepeatCount;
        public readonly short wVirtualKeyCode;
        public readonly short wVirtualScanCode;
        public readonly char UnicodeChar;
        public readonly int dwControlKeyState;
    }

    #endregion
}

使用示例:

internal class Program
{
    private static void Main(string[] args)
    {
        var kbInput = new KeyboardInput(ConsoleKey.Escape);
        kbInput.OnKeyDown += OnKeyDown;
        kbInput.OnKeyUp += OnKeyUp;

        kbInput.Run();
    }

    private static void OnKeyDown(char key, short code)
    {
        Console.WriteLine($"Key pressed: {key} (virtual code: 0x{code:X})");
    }

    private static void OnKeyUp(char key, short code)
    {
        Console.WriteLine($"Key released: {key} (virtual code: 0x{code:X})");
    }
}

0
你尝试过在循环中添加一个 keyRelease 方法吗?例如在 num++ 之后: WaitForKeyRelease(ConsoleKey.Enter);
在你的方法中:while (Console.ReadKey(true).Key == key)
你需要等待按下的键不是回车键,这就像释放键一样。

0
您可以通过以下方式检查是否输入了“enter”:

`

string input = Console.ReadLine();
if (input == "") {
     //do stuff
}

`


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