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:
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
Does Garbage Collector Call Dispose()
Is There a C# Generic Constraint for "Real Number" Types
Overloaded Method-Group Argument Confuses Overload Resolution
C# Random Numbers Aren't Being "Random"
C#: HTMLagilitypack Extract Inner Text
Wix Silent Install Unable to Launch Built in .Exe: Wix V3
Why Use Httpclient for Synchronous Connection
Do the New C# 5.0 'Async' and 'Await' Keywords Use Multiple Cores
Use Stringformat to Add a String to a Wpf Xaml Binding
Webapi Formdata Upload (To Db) with Extra Parameters
Why Can't I Declare C# Methods Virtual and Static
Generic C# Code and the Plus Operator
Windows Forms Graphic Issue on Windows 10 Os
Configureawait Pushes the Continuation to a Pool Thread
Filesystemwatcher Fails to Access Network Drive
In-Use Files Not Updated by Msi-Installer (Visual Studio Installer Project)