【问题标题】:Listen on ESC while reading Console line在读取控制台行时听 ESC
【发布时间】:2015-11-06 21:50:55
【问题描述】:

我想将用户输入读入字符串,同时仍随时对 ESC 按下作出反应,但没有定义系统范围的热键。

所以当用户键入 e 时。 G。 “测试名称”,但不是按 ENTER 确认,而是按 ESC,他应该被引导回到主菜单。

Console.Write("Enter name: ")
if (Console.ReadLine().Contains(ConsoleKey.Escape.ToString()))
{
    goto MainMenu;
}
return Console.ReadLine();

这是我能想到的最简单的方法,但由于Console.ReadLine() 没有看到 ESC,所以它不起作用。

在开始输入文本here 之前按下 ESC 时发现了一种相当复杂的反应方式,但我希望它随时都能工作。

【问题讨论】:

    标签: c# console key readline hotkeys


    【解决方案1】:

    您可能不得不放弃使用ReadLine 并使用ReadKey 自己动手:

    static void Main(string[] args)
    {
        Console.Clear();
        Console.Write("Enter your name and press ENTER.  (ESC to cancel): ");
        string name = readLineWithCancel();
    
        Console.WriteLine("\r\n{0}", name == null ? "Cancelled" : name);
    
        Console.ReadLine();
    }
    
    //Returns null if ESC key pressed during input.
    private static string readLineWithCancel()
    {
        string result = null;
    
        StringBuilder buffer = new StringBuilder();
    
        //The key is read passing true for the intercept argument to prevent
        //any characters from displaying when the Escape key is pressed.
        ConsoleKeyInfo info = Console.ReadKey(true);
        while (info.Key != ConsoleKey.Enter && info.Key != ConsoleKey.Escape)
        {
            Console.Write(info.KeyChar);
            buffer.Append(info.KeyChar);
            info = Console.ReadKey(true);
        } 
    
        if (info.Key == ConsoleKey.Enter)
        {
            result = buffer.ToString();
        }
    
        return result;
    }
    

    此代码不完整,可能需要进行一些工作才能使其健壮,但它应该可以为您提供一些想法。

    【讨论】:

    • 感谢您的回复。看起来比我的效率更高,虽然我必须添加对退格的支持;)
    • 这似乎不适用于退格键。它将光标向后移动,但不删除字母
    【解决方案2】:

    Chris Dunaway 的one 的改进版:

        public static bool CancelableReadLine(out string value)
        {
            value = string.Empty;
            var buffer = new StringBuilder();
            var key = Console.ReadKey(true);
            while (key.Key != ConsoleKey.Enter && key.Key != ConsoleKey.Escape)
            {
                if (key.Key == ConsoleKey.Backspace && Console.CursorLeft > 0)
                {
                    var cli = --Console.CursorLeft;
                    buffer.Remove(cli, 1);
                    Console.CursorLeft = 0;
                    Console.Write(new String(Enumerable.Range(0, buffer.Length + 1).Select(o => ' ').ToArray()));
                    Console.CursorLeft = 0;
                    Console.Write(buffer.ToString());
                    Console.CursorLeft = cli;
                    key = Console.ReadKey(true);
                }
                else if (Char.IsLetterOrDigit(key.KeyChar) || Char.IsWhiteSpace(key.KeyChar))
                {
                    var cli = Console.CursorLeft;
                    buffer.Insert(cli, key.KeyChar);
                    Console.CursorLeft = 0;
                    Console.Write(buffer.ToString());
                    Console.CursorLeft = cli + 1;
                    key = Console.ReadKey(true);
                }
                else if (key.Key == ConsoleKey.LeftArrow && Console.CursorLeft > 0)
                {
                    Console.CursorLeft--;
                    key = Console.ReadKey(true);
                }
                else if (key.Key == ConsoleKey.RightArrow && Console.CursorLeft < buffer.Length)
                {
                    Console.CursorLeft++;
                    key = Console.ReadKey(true);
                }
                else
                {
                    key = Console.ReadKey(true);
                }
            }
    
            if (key.Key == ConsoleKey.Enter)
            {
                Console.WriteLine();
                value = buffer.ToString();
                return true;
            }
            return false;
        }
    }
    

    我没有测试太多,但至少对我有用。

    【讨论】:

      【解决方案3】:

      如果有人遇到同样的问题。我自己找到了解决方案。可能会短一点,但至少它有效:

                      string name;
                      char CurrentChar = ' ';                    
                      do
                      {
                          CurrentChar = Console.ReadKey().KeyChar;
                          if (CurrentChar != '\b')
                          {
                              name = name + CurrentChar;
                              if (CurrentChar == (char)13)
                              {
                                  return name;
                              }
                          }
                          else if (Console.CursorLeft >= 14)
                          {
                              name = name.Remove(name.Length - 1);
                              Console.Write(" \b");
                          }
                          else
                          {
                              Console.CursorLeft = 14;
                          }
      
                      } while (CurrentChar != (char)27);
      

      【讨论】:

        【解决方案4】:

        oleg wx 的一点改进版

        现在可以在这种情况下使用

        static void Main(string[] args)
        {
            Console.Write("Text: ");
            CancelableReadConsoleLine(out string value);
        }
        
        public static bool CancelableReadConsoleLine(out string line)
        {
            var clOffset = Console.CursorLeft;
            line = string.Empty;
            var buffer = new StringBuilder();
            var key = Console.ReadKey(true);
            while (key.Key != ConsoleKey.Enter && key.Key != ConsoleKey.Escape)
            {
                if (key.Key == ConsoleKey.Backspace && Console.CursorLeft - clOffset > 0)
                {
                    var cli = Console.CursorLeft - clOffset - 1;
                    buffer.Remove(cli, 1);
                    Console.CursorLeft = clOffset;
                    Console.Write(new string(' ', buffer.Length + 1));
                    Console.CursorLeft = clOffset;
                    Console.Write(buffer.ToString());
                    Console.CursorLeft = cli + clOffset;
                    key = Console.ReadKey(true);
                }
                else if (key.Key == ConsoleKey.Delete && Console.CursorLeft - clOffset < buffer.Length)
                {
                    var cli = Console.CursorLeft - clOffset;
                    buffer.Remove(cli, 1);
                    Console.CursorLeft = clOffset;
                    Console.Write(new string(' ', buffer.Length + 1));
                    Console.CursorLeft = clOffset;
                    Console.Write(buffer.ToString());
                    Console.CursorLeft = cli + clOffset;
                    key = Console.ReadKey(true);
                }
                else if (char.IsLetterOrDigit(key.KeyChar) || char.IsWhiteSpace(key.KeyChar))
                {
                    var cli = Console.CursorLeft - clOffset;
                    buffer.Insert(cli, key.KeyChar);
                    Console.CursorLeft = clOffset;
                    Console.Write(buffer.ToString());
                    Console.CursorLeft = cli + clOffset + 1;
                    key = Console.ReadKey(true);
                }
                else if (key.Key == ConsoleKey.LeftArrow && Console.CursorLeft - clOffset > 0)
                {
                    Console.CursorLeft--;
                    key = Console.ReadKey(true);
                }
                else if (key.Key == ConsoleKey.RightArrow && Console.CursorLeft - clOffset < buffer.Length)
                {
                    Console.CursorLeft++;
                    key = Console.ReadKey(true);
                }
                else
                {
                    key = Console.ReadKey(true);
                }
            }
        
            if (key.Key == ConsoleKey.Enter)
            {
                Console.WriteLine();
                line = buffer.ToString();
                return true;
            }
            return false;
        }
        

        【讨论】:

          【解决方案5】:

          可能还有很多需要改进的地方,但我想我已经用这个解决方案涵盖了大部分基础知识:

          此代码处理:

          • 超出控制台窗口边缘写入
          • 在现有文本后调用方法(使用 Console.Write)
          • 调整控制台窗口的大小
          • Ctrl 修饰符

          此代码无法处理:

          • Tab 键(Normal Console.ReadLine 在 Tab 键超出窗口大小时会出现错误,因此选择将其排除在外,因为它很少需要...)

          我的代码基于 oleg wxChris Dunaway's 的答案,因此也归功于他们。

          我建议尝试根据这些答案构建自己的,但有时您只需要一些工作即可,在这种情况下,当我在我的应用程序中使用该代码时,该代码对我有用:


          只需新建一个静态类,例如:

          public static class XConsole
          {
          
          }
          

          并在其中粘贴以下方法:

              public static string CancelableReadLine(out bool isCancelled)
              {
                  var cancelKey = ConsoleKey.Escape;
                  var builder = new StringBuilder();
                  var cki = Console.ReadKey(true);
                  int index = 0;
                  (int left, int top) startPosition;
          
                  while (cki.Key != ConsoleKey.Enter && cki.Key != cancelKey)
                  {
                      if (cki.Key == ConsoleKey.LeftArrow)
                      {
                          if (index < 1)
                          {
                              cki = Console.ReadKey(true);
                              continue;
                          }
          
                          LeftArrow(ref index, cki);
                      }
                      else if (cki.Key == ConsoleKey.RightArrow)
                      {
                          if (index >= builder.Length)
                          {
                              cki = Console.ReadKey(true);
                              continue;
                          }
          
                          RightArrow(ref index, cki, builder);
                      }
                      else if (cki.Key == ConsoleKey.Backspace)
                      {
                          if (index < 1)
                          {
                              cki = Console.ReadKey(true);
                              continue;
                          }
          
                          BackSpace(ref index, cki, builder);
                      }
                      else if (cki.Key == ConsoleKey.Delete)
                      {
                          if (index >= builder.Length)
                          {
                              cki = Console.ReadKey(true);
                              continue;
                          }
          
                          Delete(ref index, cki, builder);
                      }
                      else if (cki.Key == ConsoleKey.Tab)
                      {
                          cki = Console.ReadKey(true);
                          continue;
                      }
                      else
                      {
                          if (cki.KeyChar == '\0')
                          {
                              cki = Console.ReadKey(true);
                              continue;
                          }
          
                          Default(ref index, cki, builder);
                      }
          
                      cki = Console.ReadKey(true);
                  }
          
                  if (cki.Key == cancelKey)
                  {
                      startPosition = GetStartPosition(index);
                      ErasePrint(builder, startPosition);
          
                      isCancelled = true;
                      return string.Empty;
                  }
          
                  isCancelled = false;
          
                  startPosition = GetStartPosition(index);
                  var endPosition = GetEndPosition(startPosition.left, builder.Length);
                  var left = 0;
                  var top = startPosition.top + endPosition.top + 1;
          
                  Console.SetCursorPosition(left, top);
          
                  var value = builder.ToString();
                  return value;
              }
          
              private static void LeftArrow(ref int index, ConsoleKeyInfo cki)
              {
                  var previousIndex = index;
                  index--;
          
                  if (cki.Modifiers == ConsoleModifiers.Control)
                  {
                      index = 0;
          
                      var startPosition = GetStartPosition(previousIndex);
                      Console.SetCursorPosition(startPosition.left, startPosition.top);
          
                      return;
                  }
          
                  if (Console.CursorLeft > 0)
                      Console.CursorLeft--;
                  else
                  {
                      Console.CursorTop--;
                      Console.CursorLeft = Console.BufferWidth - 1;
                  }
              }
          
              private static void RightArrow(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
              {
                  var previousIndex = index;
                  index++;
          
                  if (cki.Modifiers == ConsoleModifiers.Control)
                  {
                      index = builder.Length;
          
                      var startPosition = GetStartPosition(previousIndex);
                      var endPosition = GetEndPosition(startPosition.left, builder.Length);
                      var top = startPosition.top + endPosition.top;
                      var left = endPosition.left;
          
                      Console.SetCursorPosition(left, top);
          
                      return;
                  }
          
                  if (Console.CursorLeft < Console.BufferWidth - 1)
                      Console.CursorLeft++;
                  else
                  {
                      Console.CursorTop++;
                      Console.CursorLeft = 0;
                  }
              }
          
              private static void BackSpace(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
              {
                  var previousIndex = index;
                  index--;
          
                  var startPosition = GetStartPosition(previousIndex);
                  ErasePrint(builder, startPosition);
          
                  builder.Remove(index, 1);
                  Console.Write(builder.ToString());
          
                  GoBackToCurrentPosition(index, startPosition);
              }
          
              private static void Delete(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
              {
                  var startPosition = GetStartPosition(index);
                  ErasePrint(builder, startPosition);
          
                  if (cki.Modifiers == ConsoleModifiers.Control)
                  {
                      builder.Remove(index, builder.Length - index);
                      Console.Write(builder.ToString());
          
                      GoBackToCurrentPosition(index, startPosition);
                      return;
                  }
          
                  builder.Remove(index, 1);
                  Console.Write(builder.ToString());
          
                  GoBackToCurrentPosition(index, startPosition);
              }
          
              private static void Default(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
              {
                  var previousIndex = index;
                  index++;
          
                  builder.Insert(previousIndex, cki.KeyChar);
          
                  var startPosition = GetStartPosition(previousIndex);
                  Console.SetCursorPosition(startPosition.left, startPosition.top);
                  Console.Write(builder.ToString());
          
                  GoBackToCurrentPosition(index, startPosition);
              }
          
              private static (int left, int top) GetStartPosition(int previousIndex)
              {
                  int top;
                  int left;
          
                  if (previousIndex <= Console.CursorLeft)
                  {
                      top = Console.CursorTop;
                      left = Console.CursorLeft - previousIndex;
                  }
                  else
                  {
                      var decrementValue = previousIndex - Console.CursorLeft;
                      var rowsFromStart = decrementValue / Console.BufferWidth;
                      top = Console.CursorTop - rowsFromStart;
                      left = decrementValue - rowsFromStart * Console.BufferWidth;
          
                      if (left != 0)
                      {
                          top--;
                          left = Console.BufferWidth - left;
                      }
                  }
          
                  return (left, top);
              }
          
              private static void GoBackToCurrentPosition(int index, (int left, int top) startPosition)
              {
                  var rowsToGo = (index + startPosition.left) / Console.BufferWidth;
                  var rowIndex = index - rowsToGo * Console.BufferWidth;
          
                  var left = startPosition.left + rowIndex;
                  var top = startPosition.top + rowsToGo;
          
                  Console.SetCursorPosition(left, top);
              }
          
              private static (int left, int top) GetEndPosition(int startColumn, int builderLength)
              {
                  var cursorTop = (builderLength + startColumn) / Console.BufferWidth;
                  var cursorLeft = startColumn + (builderLength - cursorTop * Console.BufferWidth);
          
                  return (cursorLeft, cursorTop);
              }
          
              private static void ErasePrint(StringBuilder builder, (int left, int top) startPosition)
              {
                  Console.SetCursorPosition(startPosition.left, startPosition.top);
                  Console.Write(new string(Enumerable.Range(0, builder.Length).Select(o => ' ').ToArray()));
          
                  Console.SetCursorPosition(startPosition.left, startPosition.top);
              }
          

          你现在可以这样称呼它:

                  Console.WriteLine("Calling at start of screen");
                  string text = XConsole.CancelableReadLine(out bool isCancelled);
          
                  if (isCancelled)
                  {
                      //Do what you want in here, for instance: 
                      return;
                  }
          

          你也可以在 Console.Write 之后调用它:

                  Console.WriteLine("Calling after Console.Write");
                  Console.Write("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
                  string text = XConsole.CancelableReadLine(out bool isCancelled);
          
                  if (isCancelled) 
                      return;
          

          希望这会有所帮助!

          【讨论】:

            【解决方案6】:

            这是最简单的方法:

            Console.WriteLine("Press ESC to go to Main Menu");
            
            while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
            {
                // your code here
            }
            goto MainMenu;
            

            【讨论】:

            • 这对我不起作用:当进入 while 循环时,Console.ReadLine() 正在停止线程,直到按下 ENTER。
            猜你喜欢
            • 2016-03-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-05
            • 1970-01-01
            • 2012-03-19
            • 2021-07-02
            相关资源
            最近更新 更多