C# Arrow Key Input for a Console App

C# arrow key input for a console app

A bit late now, but here's how to access keyboard state in a console application.

Note that it's not all managed code as it requires GetKeyState to be imported from User32.dll.

/// <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);
}

How do I make the cmd take arrow input in C#?

You can handle arrow input in console using ReadKey() method.

var key = Console.ReadKey().Key;
if (key == ConsoleKey.DownArrow)
Console.WriteLine("Down arrow pressed");

Arrows keys have codes ConsoleKey.UpArrow, ConsoleKey.DownArrow, ConsoleKey.LeftArrow and ConsoleKey.RightArrow.

Controlling menu with the arrow keys and enter

Instead of writing every combination by hand, here is what I would do: Use a helper class for common tasks like these kind of multiple-choice actions, pass a set of options and wait for the user to make a selection.

public class ConsoleHelper
{
public static int MultipleChoice(bool canCancel, params string[] options)
{
const int startX = 15;
const int startY = 8;
const int optionsPerLine = 3;
const int spacingPerLine = 14;

int currentSelection = 0;

ConsoleKey key;

Console.CursorVisible = false;

do
{
Console.Clear();

for (int i = 0; i < options.Length; i++)
{
Console.SetCursorPosition(startX + (i % optionsPerLine) * spacingPerLine, startY + i / optionsPerLine);

if(i == currentSelection)
Console.ForegroundColor = ConsoleColor.Red;

Console.Write(options[i]);

Console.ResetColor();
}

key = Console.ReadKey(true).Key;

switch (key)
{
case ConsoleKey.LeftArrow:
{
if (currentSelection % optionsPerLine > 0)
currentSelection--;
break;
}
case ConsoleKey.RightArrow:
{
if (currentSelection % optionsPerLine < optionsPerLine - 1)
currentSelection++;
break;
}
case ConsoleKey.UpArrow:
{
if (currentSelection >= optionsPerLine)
currentSelection -= optionsPerLine;
break;
}
case ConsoleKey.DownArrow:
{
if (currentSelection + optionsPerLine < options.Length)
currentSelection += optionsPerLine;
break;
}
case ConsoleKey.Escape:
{
if (canCancel)
return -1;
break;
}
}
} while (key != ConsoleKey.Enter);

Console.CursorVisible = true;

return currentSelection;
}
}

The one above for example can be used like this:

int selectedClass = ConsoleHelper.MultipleChoice(true, "Warrior", "Bard", "Mage", "Archer", 
"Thief", "Assassin", "Cleric", "Paladin", "etc.");

selectedClass will simply be the index of the selected option when the function returns (or -1 if the user pressed escape). You might want to add additional parameters like a banner text ("Select class") or formatting options.

Should look something like this:

Console output

You can of course add additional marks like _these_ or > those < to further highlight the current selection.

Using Arrow Keys to navigate in the Console

Seems like you're DllImporting msvcrt.dll to use _getch()

Try using Console.ReadKey()

ConsoleKeyInfo keyInfo = Console.ReadKey();
if (keyInfo.Key == ConsoleKey.UpArrow) {

} else if (keyInfo.Key == ConsoleKey.DownArrow) {

} ...

Listen for key press in .NET console app

Use Console.KeyAvailable so that you only call ReadKey when you know it won't block:

Console.WriteLine("Press ESC to stop");
do {
while (! Console.KeyAvailable) {
// Do something
}
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);

Show keyboard inputs live as they are typed (C# Console app)

I've changed my answer, this is closer to what you wanted. I've found a keydown event implementation in this thread (NativeKeyboard internal class):
C# arrow key input for a console app

Solution lies with multiple threading timers checking values or conditions in conjunction. Main program thread is in a loop until exit condition is met(pressing ESC key). There is a prompter timer which prints a cache of values denoting a list of pressed keys, to the same line after line is cleared with white spaces. For every ConsoleKey enum, a timer is created and timer-key duo is mapped. Whenever a key is pressed, that key's mapped timer will be able to determine that specific key is pressed and updates the list with key's value/string. Whenever a key is released, list is also updated with an empty string value for that key.

I had to add LocalModifiers enum as key codes for ALT,CTRL,SHIFT and CAPS LOCK.

I had to use lock() to make sure all those timers could work without creating a concurrency problem.

When I executed the code, It had the output very close to what you described. Sometime a few of the pressed keys are not shown in case of 7-8 keys are pressed.

Here is my code:

 class Program
{
public static Dictionary<int, String> inputMap = new Dictionary<int, string>();
public static Dictionary<Timer, ConsoleKey> TimerKeyMap = new Dictionary<Timer, ConsoleKey>();
public static Dictionary<Timer, LocalModifiers> TimerModifierMap = new Dictionary<Timer, LocalModifiers>();
public static bool continueLoop = true;
public static object locker = new object();
static void Main(string[] args)
{
Program program = new Program();
program.run();
}
public enum LocalModifiers :int
{
SHIFT = 0x10,
CTL = 0x11,
ALT = 0x12,
CAPS_LOCK = 0x14
}
public void run()
{
Timer keyPressedPrompter = new Timer();
keyPressedPrompter.Interval = 60;
keyPressedPrompter.Elapsed += new ElapsedEventHandler(KeyPressedPrompterEvent);
keyPressedPrompter.Enabled = true;
foreach (ConsoleKey key in Enum.GetValues(typeof(ConsoleKey)))
{
Timer timer = new Timer();
TimerKeyMap[timer] = key;
timer.Interval = 60;
timer.Elapsed += new ElapsedEventHandler(KeyPressedCheckerEvent);
timer.Enabled = true;
}
foreach (LocalModifiers key in Enum.GetValues(typeof(LocalModifiers)))
{
Timer timer = new Timer();
TimerModifierMap[timer] = key;
timer.Interval = 60;
timer.Elapsed += new ElapsedEventHandler(ModifierPressedCheckerEvent);
timer.Enabled = true;
}
Console.WriteLine("Current inputs:");
while (continueLoop) { }
}
public static void ModifierPressedCheckerEvent(object source, EventArgs e)
{
lock (locker)
{
if (NativeKeyboard.IsKeyDown((int)TimerModifierMap[(Timer)source]))
{
//Console.WriteLine(KeyTimerMapReverse[(Timer)source].ToString()+ " pressed");
inputMap[(int)TimerModifierMap[(Timer)source]] = TimerModifierMap[(Timer)source].ToString() + " ";
}
else
{
// Console.WriteLine(KeyTimerMapReverse[(Timer)source].ToString() + " released");
inputMap[(int)TimerModifierMap[(Timer)source]] = "";

}
}
}
public static void KeyPressedCheckerEvent(object source, EventArgs e)
{
lock (locker)
{
if (NativeKeyboard.IsKeyDown((int)TimerKeyMap[(Timer)source]))
{
if (TimerKeyMap[(Timer)source] == ConsoleKey.Escape)
continueLoop = false;
//Console.WriteLine(KeyTimerMapReverse[(Timer)source].ToString()+ " pressed");
inputMap[(int)TimerKeyMap[(Timer)source]] = TimerKeyMap[(Timer)source].ToString() + " ";
}
else
{
// Console.WriteLine(KeyTimerMapReverse[(Timer)source].ToString() + " released");
inputMap[(int)TimerKeyMap[(Timer)source]] = "";

}
}
}
public static void KeyPressedPrompterEvent(object source, EventArgs e)
{
Console.Write(" ");//clear the line - - can be extended
Console.Write("\r");

lock (locker)
{
foreach (KeyValuePair<int, String> entry in inputMap)
{
// do something with entry.Value or entry.Key
Console.Write(entry.Value);
}
}

}
}

/// <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(int 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);
}

Disallow console to print any keys and take only arrows as input

You have to filter the output to the standart output stream.

The console uses a text writer to output the data coming from the standard input, and this text writer is invoked BEFORE the console gives you the key by Console.ReadKey(), therefore, you cannot cancel the outputting of the characters pressed. BUT!

You can set a custom text writer by Console.SetOut();

The code below sets a text writer which will filter everything. Only the Write(char) method is overridden and it is sufficient as far as I can see, if not, you can implement others.

When you need to actually write to the console, swap the text writer to a default one having standart output stream as the base stream, and voila:

class ConsoleFilteredOutput : TextWriter
{
public override void Write(char value)
{

}

public override Encoding Encoding
{
get { return Encoding.Unicode; }
}
}

static TextWriter standardOutputWriter = Console.Out;
static ConsoleFilteredOutput filteredOutputWriter = new ConsoleFilteredOutput();

static void WriteUnfiltered(string text)
{
Console.SetOut(standardOutputWriter);
Console.WriteLine(text);
Console.SetOut(filteredOutputWriter);
}

static void Main(string[] args)
{
Console.SetOut(filteredOutputWriter);

do
{
ConsoleKeyInfo keyinfo = Console.ReadKey();
switch (keyinfo.Key)
{
case ConsoleKey.DownArrow:
case ConsoleKey.LeftArrow:
case ConsoleKey.UpArrow:
case ConsoleKey.RightArrow:
WriteUnfiltered(keyinfo.Key.ToString());
break;
}
}
while (true);
}
}

`



Related Topics



Leave a reply



Submit