控制台应用程序的 C# 箭头键输入

Posted

技术标签:

【中文标题】控制台应用程序的 C# 箭头键输入【英文标题】:C# arrow key input for a console app 【发布时间】:2011-05-20 01:43:44 【问题描述】:

我有一个用 C# 编写的简单控制台应用程序。我希望能够检测到方向键按下,这样我就可以让用户转向。如何使用控制台应用检测 keydown/keyup 事件?

我所有的谷歌搜索都导致了有关 Windows 窗体的信息。我没有图形用户界面。这是一个控制台应用程序(通过串行端口控制机器人)。

我编写了处理这些事件的函数,但我不知道如何注册才能实际接收这些事件:

  private void myKeyDown(object sender, KeyEventArgs e)
  
      switch (e.KeyCode)
      
          case Keys.Left:
                 ...
          case Keys.Right:
                 ...
          case Keys.Up:
                 ...
      
  

  private void myKeyUp(object sender, KeyEventArgs e)
  
      ... pretty much the same as myKeyDown
  

这可能是一个非常基本的问题,但我对 C# 还很陌生,而且我以前从未需要过这种输入。

更新:很多人建议我使用System.Console.ReadKey(true).Key。这无济于事。我需要知道一个键被按下的那一刻,当它被释放时,支持同时按住多个键。此外,ReadKey 是一个阻塞调用——这意味着程序将停止并等待按键被按下。

更新:似乎唯一可行的方法是使用 Windows 窗体。这很烦人,因为我不能在无头系统上使用它。要求表单 GUI 接收键盘输入是……愚蠢的。

但无论如何,为了后代,这是我的解决方案。我在我的 .sln 中创建了一个新的 Form 项目:

    private void Form1_Load(object sender, EventArgs e)
    
        try
        
            this.KeyDown += new KeyEventHandler(Form1_KeyDown);
            this.KeyUp += new KeyEventHandler(Form1_KeyUp);
        
        catch (Exception exc)
        
            ...
        
    

    void Form1_KeyDown(object sender, KeyEventArgs e)
    
        switch (e.KeyCode)
        
            // handle up/down/left/right
            case Keys.Up:
            case Keys.Left:
            case Keys.Right:
            case Keys.Down:
            default: return;  // ignore other keys
        
    

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    
        // undo what was done by KeyDown
    

请注意,如果您按住某个键,KeyDown 将被多次调用,而 KeyUp 只会被调用一次(当您释放它时)。所以你需要优雅地处理重复的 KeyDown 调用。

【问题讨论】:

您可以在不同的线程中调用 ReadKey,请参阅我的旋转线程建议。虽然这并没有解决 keyup/keydown 的问题。 【参考方案1】:

现在有点晚了,但这里是在控制台应用程序中访问键盘状态的方法。

请注意,它并不都是托管代码,因为它需要从 User32.dll 导入 GetKeyState。

/// <summary>
/// Codes representing keyboard keys.
/// </summary>
/// <remarks>
/// Key code documentation:
/// http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx
/// </remarks>
internal enum KeyCode : int

    /// <summary>
    /// The left arrow key.
    /// </summary>
    Left = 0x25,

    /// <summary>
    /// The up arrow key.
    /// </summary>
    Up,

    /// <summary>
    /// The right arrow key.
    /// </summary>
    Right,

    /// <summary>
    /// The down arrow key.
    /// </summary>
    Down


/// <summary>
/// Provides keyboard access.
/// </summary>
internal static class NativeKeyboard

    /// <summary>
    /// A positional bit flag indicating the part of a key state denoting
    /// key pressed.
    /// </summary>
    private const int KeyPressed = 0x8000;

    /// <summary>
    /// Returns a value indicating if a given key is pressed.
    /// </summary>
    /// <param name="key">The key to check.</param>
    /// <returns>
    /// <c>true</c> if the key is pressed, otherwise <c>false</c>.
    /// </returns>
    public static bool IsKeyDown(KeyCode key)
    
        return (GetKeyState((int)key) & KeyPressed) != 0;
    

    /// <summary>
    /// Gets the key state of a key.
    /// </summary>
    /// <param name="key">Virtuak-key code for key.</param>
    /// <returns>The state of the key.</returns>
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern short GetKeyState(int key);

【讨论】:

有趣。这需要我自己轮询键盘,这意味着我不能只拥有一个基于事件的应用程序。但这很简单。 是的,控制台应用程序通常没有消息循环,但没有什么可以阻止您自己滚动,然后让应用程序的其余部分成为事件驱动的。【参考方案2】:
var isUp = Console.ReadKey().Key == ConsoleKey.UpArrow;

或另一个示例,仅针对您的情况:

while (true)

   var ch = Console.ReadKey(false).Key;
   switch(ch)
   
       case ConsoleKey.Escape:
          ShutdownRobot();
          return;
       case ConsoleKey.UpArrow:
          MoveRobotUp();
          break;
       case ConsoleKey.DownArrow:
          MoveRobotDown();
          break;
   

【讨论】:

【参考方案3】:
System.Console.ReadKey(true).Key == ConsoleKey.UpArrow

你可以把它变成一个旋转,比如:

while(Running)

  DoStuff();
  System.Console.ReadKey(true).Key == ConsoleKey.UpArrow
  Thread.Sleep(1)

【讨论】:

按住一个键,还是按住多个键呢?我不只是想按下最新的键 - 我想处理多个键被按住(或根本没有键按下)。 那你需要直接处理键盘消息。最简单的方法是在 Windows 上使用 DirectInput。 如果一定要手动的话,相关的Win32函数有GetConsoleWindow和SetWindowsHookEx。挂钩控制台的 'WH_CALLWNDPROC' 事件以捕获原始 Windows 消息。控制台并没有获得 Windows 窗体所做的太多抽象,尽管它可能具有您正在寻找的功能,事实上没有人建议它向我暗示这是您的下一个合乎逻辑的步骤。 如果您打算创建一个高度交互的应用程序,您最好在 Windows 窗体中创建一个人造控制台。这是一种用于创建基本视频游戏 UI 的流行技术。 请记住,在键盘上的每个键与 shift、ctrl、alt 等修饰符之间,您可以使用数百个组合键。因此,为什么想要更多功能并不完全是标准功能。【参考方案4】:

我和你发现了同样的问题,在这里,一个使用任务的有趣帖子。 原始帖子可以在这里找到: C# Console Application - How do I always read input from the console?

我必须通过 Raspberry GPIO(使用单声道 C#)模拟 PWM 输出来测试 LCD 背光。我想用两个简单的键来改变占空比(上/下)和一个额外的键来停止程序。

我试过这个(变量):

static ConsoleKeyInfo key = new ConsoleKeyInfo();
static int counter = 0;
static int duty = 5; // Starts in 50%

主程序:

static void Main(string[] args)

// cancellation by keyboard string
    CancellationTokenSource cts = new CancellationTokenSource();
    // thread that listens for keyboard input
    var kbTask = Task.Run(() =>
    
        while (true)
        
            key = Console.ReadKey(true);
            if (key.KeyChar == 'x' || key.KeyChar == 'X')
            
                cts.Cancel();
                break;
            
            else if (key.KeyChar == 'W' || key.KeyChar == 'w')
            
                if (duty < 10)
                    duty++;
                //Console.WriteLine("\tIncrementa Ciclo");
                //mainAppState = StateMachineMainApp.State.TIMER;
                //break;
            
            else if (key.KeyChar == 'S' || key.KeyChar == 's')
            
                if (duty > 0)
                    duty--;
                //Console.WriteLine("\tDecrementa Ciclo");
                //mainAppState = StateMachineMainApp.State.TIMER;
                // break;
            
        
    );

    // thread that performs main work
    Task.Run(() => DoWork(), cts.Token);

    string OsVersion = Environment.OSVersion.ToString();
    Console.WriteLine("Sistema operativo: 0", OsVersion);
    Console.WriteLine("Menú de Progama:");
    Console.WriteLine(" W. Aumentar ciclo útil");
    Console.WriteLine(" S. Disminuir ciclo útil");
    Console.WriteLine(" X. Salir del programa");

    Console.WriteLine();
    // keep Console running until cancellation token is invoked
    kbTask.Wait();


static void DoWork()

    while (true)
    
        Thread.Sleep(50);
        if (counter < 10)
        
            if (counter < duty)
                Console.Write("─");
                //Console.WriteLine(counter + " - ON");
            else
                Console.Write("_");
                //Console.WriteLine(counter + " - OFF");
            counter++;
        
        else
        
            counter = 0;
        
    

当需要增加占空比时,按'W'键使主任务改变占空比变量(duty);与'S'键减量相同。当按下“X”键时,程序结束。

【讨论】:

【参考方案5】:

我晚了 10 年,但我想与其他可能在未来阅读此主题的人分享。

有一种更简单的方法可以实现 Ergwun 所说的。

您可以在一行代码中实现结果,而不是创建 Enum 和 Class。

[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern short GetAsyncKeyState(int key); 
   
. . .

bool[] keys = new bool[4];

for (int i = 0; i < 4; i++)                            
    keys[i] = (0x8000 & GetAsyncKeyState((char)"\x25\x26\x27\x28"[i])) != 0;

您需要从 User32.dll 导入 GetKeyState 或在我的情况下为 GetAsyncKeyState 方法。

然后你只需要循环并检查是否有任何键被按下。 字符串中这些奇怪的数字分别是左、上、右和下箭头键的Virtual-Key Codes。

布尔结果存储在一个数组中,稍后可以循环查看是否按下了某个键。您只需要为您的程序定义数组中键的顺序

【讨论】:

【参考方案6】:

你可以这样做

bool keyWasPressed = false;
if (consolekey.avalable)

    keyvar = console.readkey(true);
    keyWasPressed = true;

if(keyWasPressed)

    // enter you code here using keyvar

else

    // the commands that happen if you don't press anything

【讨论】:

以上是关于控制台应用程序的 C# 箭头键输入的主要内容,如果未能解决你的问题,请参考以下文章

在 mfc 的 cedit 控制中处理箭头键

如何在 Linux 中获取箭头键并在键盘上输入键以表现得像 Windows 7

使用标准输入识别箭头键

C# 虚拟键修饰键使用发送输入法失败

更改 MFC 应用程序的输入控件

用户按箭头键时检测C ++