Страницы

Поиск по вопросам

четверг, 29 ноября 2018 г.

Как определять одиночные нажатия в консоли?

Привет! Я новичок в C# и меня интересует вопрос:как сделать, чтоб консоль распознавала только одиночные нажатия клавиш и игнорировала их зажатие? (И возможно ли это сделать вообще в консольном C#? UPD: Добавил код для понимания. Задача состоит в том, чтобы консоль выводила число только после одиночного нажатия клавиши, а при ее зажатии не выводила ничего.
static void Main(string[] args) { Console.WriteLine("Press \u0022Enter\u0022 to play!"); int num = 0; void WaitForKey(ConsoleKey key) { while (Console.ReadKey(true).Key != key) {
} } for (int i = 1; i <= 10; i++) { WaitForKey(ConsoleKey.Enter); Console.Write("{0}", num); num++; } }
UPD.2: новая версия кода, теперь проблема в том, что между нажатиями одной клавиши надо нажимать другую.
static void Main(string[] args) { Console.WriteLine("Press \u0022Enter\u0022 to play!"); int num = 0;
for (int i = 1; i <= 10; i++) { do { ConsoleKeyInfo keypress; ConsoleKeyInfo oldkeypress;
keypress = Console.ReadKey(true); oldkeypress = keypress;
keypress = Console.ReadKey(true); if (oldkeypress != keypress) { Console.WriteLine("{0}", num); num++; ; oldkeypress = keypress; }
} while (1==1); }


Ответ

Ваша задача сложнее чем кажется. Проблемы:
Обычные консольные программы не слушают события ввода системы и, как следствие, не могут их обрабатывать без дополнительных извращений. Методы чтения класса System.Console только читают входной поток (не обязательно стандартный), но не управляют им, поэтому вы не можете отличить многократное нажатие на клавишу с отпусканием от простого "зажатия" или "залипания" клавиши, т.к. система регистрирует все нажатия и складывает во входной поток. Из предыдущих пунктов следует, что даже с привлечением WinAPI через PInvoke, без работы и, в некотором смысле, костылей не обойтись.
Не испугало? Тогда поехали.
Я намерено оставляю за кадром варианты с подключением диспетчера сообщений, скрытыми окнами и прочие не слишком честные, по отношению к консоли, методы. Желающие могут написать это отдельным ответом. Тут только хардкор, только честная и чистая консоль + немного WinAPI(PInvoke)! =)
Нам понадобится импортировать две функции WinAPI: getkeystate(WinAPI, PInvoke) и getkeyboardstate (WinAPI, PInvoke). Сделаем это в отдельном классе.
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);
Импорт необходимого завершен, делаем проверку на "нажатость". Для примера будем обрабатывать все виртуальные клавиши, включая мышь, вы можете легко изменить это поведение добавив исключение ненужных клавиш (список клавиш тут)
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); }
Добавим пару хелперов для красоты и удобства при использовании
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); } }
Все, класс для работы с клавиатурой завершен, но вы можете добавить туда что-то свое, если будет необходимость.
Теперь как этим монстром пользоваться? Смотрим, это относительно легко, хотя свои условности конечно есть:
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); }
Весь фокус тут в том, что ожидание нажатия и отпускания клавиши сделаны не через чтение входного потока, а прямыми запросами к системе. Это позволяет в последствии читать из потока только то, что нас интересует и сбрасывать лишнее.
В описанном примере есть один недостаток, если клавиши нажимать достаточно быстро, то некоторые клавиши не будут обработаны, красивого решения пока найти не удалось, но поставленная задача в общем уже решается - программа реагирует только на одиночные нажатия.
Отдельные фрагменты можно улучшить или написать более изящно, но это всего лишь демонстрационный пример, как можно решить данную задачу не прибегая к хитрым трюкам со скрытыми окнами и т.д.

Комментариев нет:

Отправить комментарий