Your task is more difficult than it seems. Problems:
Regular console programs do not listen to system input events and, as a result, cannot process them without additional distortions.
The methods of reading the System.Console class only read the input stream (not necessarily the standard one), but do not control it, so you cannot distinguish pressing a key repeatedly with releasing it from a simple “clamping” or “sticking” key, since the system records all keystrokes and folds into the input stream.
From the preceding paragraphs, it follows that even with the involvement of WinAPI through PInvoke, without work and, in a sense, crutches are indispensable.
Not scared? Then let's go.
I intend to leave behind the scenes options with the connection of the message manager, hidden windows and other not very honest, with respect to the console methods. Those interested can write this in a separate answer. There is only hardcore, only honest and clean console + a little WinAPI (PInvoke)! =)
We will need to import two WinAPI functions: getkeystate ( WinAPI , PInvoke ) and getkeyboardstate ( WinAPI , PInvoke ). Let's do it in a separate class.
using System; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; static class ConsoleNative { //получение состояния всех клавиш, включая служебные и мышь [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetKeyboardState(byte [] lpKeyState); //сюда будем писать состояния клавиш private static byte[] keys = new byte[256]; //получение состояния указанной клавиши [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern short GetKeyState(int key);
The import of the necessary is completed, we make a check for "click". For example, we will process all virtual keys, including the mouse, you can easily change this behavior by adding an exception to unnecessary keys (the list of keys is here )
public static bool IsAnyKeyPressed() { //По совету с PInvoke, добавлено для того, чтобы нормально отрабатывала //GetKeyboardState GetKeyState(0); if(!GetKeyboardState(keys)) { //защита на всякий случай, у меня ни разу не сработала, но... int err = Marshal.GetLastWin32Error(); throw new Win32Exception(err); } //каждый байт - это состояние одной из клавиш for(int i = 0; i < 256; i++) { if(i < 7) { //байты с 0 по 6 отвечают за клавиши мыши, проигнорируем их keys[i] = 0; } else { //за состояние "нажатости" отвечает старший бит, остальное просто зануляем keys[i] = (byte)(keys[i] & 0x80); } } return keys.Any(k => k != 0); }
Add a couple of helpers for beauty and convenience when using
public static void WaitForKeyDown() { bool isAnyKeyPressed; do { isAnyKeyPressed = IsAnyKeyPressed(); } while(!isAnyKeyPressed); } public static void WaitForKeyUp() { bool isAnyKeyPressed; do { isAnyKeyPressed = IsAnyKeyPressed(); } while(isAnyKeyPressed); } //очистка входного потока от нажатых клавиш. Мы же не хотим //при следующем вызове Console.ReadXXX прочитать все, что нажимали до этого? public static void FlushInputStream() { while(Console.KeyAvailable) Console.ReadKey(true); } }
Everything, the class for working with the keyboard is completed, but you can add something of your own there, if necessary.
Now how to use this monster? We look, it is relatively easy, although of course there are some conventions:
static void Main(string[] args) { do { //ждем, пока нажмут клавишу ConsoleNative.WaitForKeyDown(); //ждем пока отпустят клавишу ConsoleNative.WaitForKeyUp(); //с помощью Console.ReadKey читаем что было нажато var key = Console.ReadKey(true); if(!Console.KeyAvailable) { //если было одиночное нажатие выводим нажатое Console.WriteLine(key.KeyChar); } else { //если было залипание чистим входной поток от мусора ConsoleNative.FlushInputStream(); } } while(true); }
The trick here is that the wait for pressing and releasing the keys is not made by reading the input stream, but by direct requests to the system. This allows later to read from the stream only what interests us and dump the excess.
In the described example, there is one drawback, if the keys are pressed quickly enough, some keys will not be processed, we have not yet managed to find a beautiful solution, but the task in general is already being solved - the program responds only to single presses.
Individual fragments can be improved or written more elegantly, but this is just a demonstration example of how this problem can be solved without resorting to clever tricks with hidden windows, etc.
Console.ReadKey()will help with this, keystrokes will be ignored. And if you still need to keep the word / line, then only write the checks yourself. - Nikolay