1

I have a console app that prompts the user for multiple inputs. I'd like the user to be able to press escape after any prompt to cancel the operation.

Something like:

if (Console.ReadKey().Key != ConsoleKey.Escape) {
  string input = Console.ReadLine();
  ...
}

The problem with this, however, is that if a key other than escape is pressed it won't be part of the input (returned from ReadLine).

Is there a way to "peek" at the next key, or otherwise do this?

Daniel
  • 47,404
  • 11
  • 101
  • 179

3 Answers3

6
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct InputRecord
{
  internal short eventType;
  internal KeyEventRecord keyEvent;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct KeyEventRecord
{
  internal bool keyDown;
  internal short repeatCount;
  internal short virtualKeyCode;
  internal short virtualScanCode;
  internal char uChar;
  internal int controlKeyState;
}

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool PeekConsoleInput(IntPtr hConsoleInput, out InputRecord buffer, int numInputRecords_UseOne, out int numEventsRead);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool ReadConsoleInput(IntPtr hConsoleInput, out InputRecord buffer, int numInputRecords_UseOne, out int numEventsRead);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetStdHandle(int nStdHandle);

static Nullable<InputRecord> PeekConsoleEvent()
{
  InputRecord ir;
  int num;
  if (!PeekConsoleInput(GetStdHandle(-10), out ir, 1, out num))
  {
    //TODO process error
  }
  if (num != 0)
    return ir;
  return null;
}
static InputRecord ReadConsoleInput()
{
  InputRecord ir;
  int num;
  if (!ReadConsoleInput(GetStdHandle(-10), out ir, 1, out num))
  {
    //TODO process error
  }
  return ir;
}
static bool PeekEscapeKey()
{
  for (; ; )
  {
    var ev = PeekConsoleEvent();
    if (ev == null)
    {
      Thread.Sleep(10);
      continue;
    }
    if (ev.Value.eventType == 1 && ev.Value.keyEvent.keyDown && ev.Value.keyEvent.virtualKeyCode == 27)
    {
      ReadConsoleInput();
      return true;
    }
    if (ev.Value.eventType == 1 && ev.Value.keyEvent.keyDown)
      return false;
    ReadConsoleInput();
  }
}

Usage example:

  for (; ; )
  {
    Console.Write("?");
    if (PeekEscapeKey())
    {
      Console.WriteLine("esc");          
      continue;
    }
    Console.Write(">");
    var line = Console.ReadLine();
    Console.WriteLine(line);
  }
Serj-Tm
  • 16,581
  • 4
  • 54
  • 61
1

To peek, you would have to use TextReader.Peek. To use that with the console, you would use Console.In.Peek(). To use the code you are thinking of:

if(Console.In.Peek() != "\0x1B") // 0x1B is the Escape Key
{
        string input = Console.ReadLine();
        ....
}

This should work. I haven't tested it, but it seems right.

JKor
  • 3,822
  • 3
  • 28
  • 36
1

Or for a slightly less elegant method than that @user823682

ConsoleKeyInfo key = Console.ReadKey().Key;
if (key != ConsoleKey.Escape) {
  string input = String.Concat(key.KeyChar,Console.ReadLine());
  ...
}
Tony Hopkinson
  • 20,172
  • 3
  • 31
  • 39
  • 1
    ToString is not overridden in ConsoleKeyInfo. It might be a better idea to use ConsoleKeyInfo.KeyChar to get the character that was pressed. – JKor Apr 27 '12 at 00:19
  • This doesn't intercept ESC so it prints `←` to the console. – Daniel Apr 27 '12 at 02:41