I am trying to do a press Q to quit thing in the console window. I dont like my current implementation. Is there a way i can async or use a callback to get keys from the console?
可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
回答1:
You can call Console.ReadKey()
from another thread, so that it doesn't block your main thread. (You can use the .Net 4 Task
or the old Thread
to start the new thread.)
class Program { static volatile bool exit = false; static void Main() { Task.Factory.StartNew(() => { while (Console.ReadKey().Key != ConsoleKey.Q) ; exit = true; }); while (!exit) { // Do stuff } } }
回答2:
You can use the KeyAvailable property (Framework 2.0) :
if (System.Console.KeyAvailable) { ConsoleKeyInfo key = System.Console.ReadKey(true);//true don't print char on console if (key.Key == ConsoleKey.Q) { //Do something } }
回答3:
I didn't find any of the exisiting answers entirely satisfactory so I wrote my own, to work with TAP and .Net 4.5.
/// <summary> /// Obtains the next character or function key pressed by the user /// asynchronously. The pressed key is displayed in the console window. /// </summary> /// <param name="cancellationToken"> /// The cancellation token that can be used to cancel the read. /// </param> /// <param name="responsiveness"> /// The number of milliseconds to wait between polling the /// <see cref="Console.KeyAvailable"/> property. /// </param> /// <returns>Information describing what key was pressed.</returns> /// <exception cref="TaskCanceledException"> /// Thrown when the read is cancelled by the user input (Ctrl+C etc.) /// or when cancellation is signalled via /// the passed <paramred name="cancellationToken"/>. /// </exception> public static async Task<ConsoleKeyInfo> ReadKeyAsync( CancellationToken cancellationToken, int responsiveness = 100) { var cancelPressed = false; var cancelWatcher = new ConsoleCancelEventHandler( (sender, args) => { cancelPressed = true; }); Console.CancelKeyPress += cancelWatcher; try { while (!cancelPressed && !cancellationToken.IsCancellationRequested) { if (Console.KeyAvailable) { return Console.ReadKey(); } await Task.Delay( responsiveness, cancellationToken); } if (cancelPressed) { throw new TaskCanceledException( "Readkey canceled by user input."); } throw new TaskCanceledException(); } finally { Console.CancelKeyPress -= cancelWatcher; } }
回答4:
Taking from all the answers here, this is my version:
public class KeyHandler { public event EventHandler KeyEvent; public void WaitForExit() { bool exit = false; do { var key = Console.ReadKey(true); //blocks until key event switch (key.Key) { case ConsoleKey.Q: exit = true; break; case ConsoleKey.T: // raise a custom event eg: Increase throttle break; } } while (!exit); } } static void Main(string[] args) { var worker = new MyEventDrivenClassThatDoesCoolStuffByItself(); worker.Start(); var keyHandler = new KeyHandler(); keyHandler.KeyEvent+= keyHandler_KeyEvent; // modify properties of your worker keyHandler.WaitForExit(); }
- It doesn't require Main to do anything in a loop, allowing it to simply orchestrate between handling keys and manipulating properties of the worker class.
- Taking the hint from @Hans, the KeyHandler doesn't need to async up a new thread since Console.ReadKey blocks until a key is received.
回答5:
Here is an implementation that I created using KeyAvailable
. This keeps a prompt at the bottom of the console window while everything "printed" to the console starts from the top.
public class Program { private static int consoleLine; private static int consolePromptLine; private static bool exit; static string clearLine = new string(' ', Console.BufferWidth - 1); public static void Main(string[] args) { StringBuilder commandCapture = new StringBuilder(10); string promptArea = "Command> "; consolePromptLine = Console.WindowTop + Console.WindowHeight - 1; ClearLine(consolePromptLine); Console.Write(promptArea); while (!exit) { // Do other stuff // Process input if (Console.KeyAvailable) { var character = Console.ReadKey(true); if (character.Key == ConsoleKey.Enter) { if (commandCapture.Length != 0) { ProcessCommand(commandCapture.ToString()); commandCapture.Clear(); ClearLine(consolePromptLine); Console.Write(promptArea); } } else { if (character.Key == ConsoleKey.Backspace) { if (commandCapture.Length != 0) { commandCapture.Remove(commandCapture.Length - 1, 1); ClearLine(consolePromptLine); Console.Write(promptArea); Console.Write(commandCapture.ToString()); } } else { commandCapture.Append(character.KeyChar); Console.SetCursorPosition(0, consolePromptLine); Console.Write(promptArea); Console.Write(commandCapture.ToString()); } } } } } private static void ProcessCommand(string command) { if (command == "start") { Task<string> testTask = new Task<string>(() => { System.Threading.Thread.Sleep(4000); return "Test Complete"; }); testTask.ContinueWith((t) => { Print(t.Result); }, TaskContinuationOptions.ExecuteSynchronously); testTask.Start(); } else if (command == "quit") { exit = true; } Print(command); consolePromptLine = Console.WindowTop + Console.WindowHeight - 1; } public static void Print(string text) { ClearLine(consoleLine); Console.WriteLine(text); consoleLine = Console.CursorTop; } public static void ClearLine(int line) { Console.SetCursorPosition(0, line); Console.Write(clearLine); Console.SetCursorPosition(0, line); } }
回答6:
Here is how I made it:
// Comments language: pt-BR // Aguarda key no console private static async Task<ConsoleKey> WaitConsoleKey ( ) { try { // Prepara retorno ConsoleKey key = default; // Aguarda uma tecla ser pressionada await Task.Run ( ( ) => key = Console.ReadKey ( true ).Key ); // Retorna a tecla return key; } catch ( Exception ex ) { throw ex; } }