Слушайте нажатие клавиш в консольном приложении .NET

224

Как я могу продолжать запускать консольное приложение, пока не нажата клавиша (например Esc, нажата?)

Я предполагаю, что это обернуто вокруг цикла времени. Мне не нравится, ReadKeyпоскольку он блокирует работу и запрашивает клавишу, а не просто продолжает и слушает нажатие клавиши.

Как это может быть сделано?

karlstackoverflow
источник

Ответы:

343

Используйте, Console.KeyAvailableчтобы вы звонили только ReadKeyтогда, когда знаете, что он не заблокируется:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
Джефф Стерн
источник
6
но если вы сделаете это вместо readLine (), вы потеряете замечательную функцию вызова истории, нажав клавишу «вверх».
v.oddou
3
@ v.oddou После последней строки просто добавьте свой, Console.ReadLine()и все готово, нет?
Гаффи
1
Не будет ли этот цикл потреблять много ресурсов ЦП / ОЗУ? Если нет, то как?
София
79

Вы можете немного изменить свой подход - используйте Console.ReadKey()для остановки приложения, но выполняйте работу в фоновом режиме:

static void Main(string[] args)
{
    var myWorker = new MyWorker();
    myWorker.DoStuff();
    Console.WriteLine("Press any key to stop...");
    Console.ReadKey();
}

В myWorker.DoStuff()функции вы затем вызываете другую функцию в фоновом потоке (используя Action<>()или Func<>()это простой способ сделать это), а затем немедленно возвращаетесь.

slugster
источник
60

Кратчайший путь:

Console.WriteLine("Press ESC to stop");

while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
{
    // do something
}

Console.ReadKey()является функцией блокировки, она останавливает выполнение программы и ожидает нажатия клавиши, но благодаря проверке Console.KeyAvailableсначала whileцикл не блокируется, а работает до тех пор, пока не Escбудет нажата кнопка.

Юлия Ашомок
источник
Самый простой пример. Спасибо.
Joelc
18

Из видео-проклятия «Создание консольных приложений .NET в C #» Джейсона Робертса по адресу http://www.pluralsight.com

Мы могли бы сделать следующее, чтобы иметь несколько запущенных процессов

  static void Main(string[] args)
    {
        Console.CancelKeyPress += (sender, e) =>
        {

            Console.WriteLine("Exiting...");
            Environment.Exit(0);
        };

        Console.WriteLine("Press ESC to Exit");

        var taskKeys = new Task(ReadKeys);
        var taskProcessFiles = new Task(ProcessFiles);

        taskKeys.Start();
        taskProcessFiles.Start();

        var tasks = new[] { taskKeys };
        Task.WaitAll(tasks);
    }

    private static void ProcessFiles()
    {
        var files = Enumerable.Range(1, 100).Select(n => "File" + n + ".txt");

        var taskBusy = new Task(BusyIndicator);
        taskBusy.Start();

        foreach (var file in files)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Procesing file {0}", file);
        }
    }

    private static void BusyIndicator()
    {
        var busy = new ConsoleBusyIndicator();
        busy.UpdateProgress();
    }

    private static void ReadKeys()
    {
        ConsoleKeyInfo key = new ConsoleKeyInfo();

        while (!Console.KeyAvailable && key.Key != ConsoleKey.Escape)
        {

            key = Console.ReadKey(true);

            switch (key.Key)
            {
                case ConsoleKey.UpArrow:
                    Console.WriteLine("UpArrow was pressed");
                    break;
                case ConsoleKey.DownArrow:
                    Console.WriteLine("DownArrow was pressed");
                    break;

                case ConsoleKey.RightArrow:
                    Console.WriteLine("RightArrow was pressed");
                    break;

                case ConsoleKey.LeftArrow:
                    Console.WriteLine("LeftArrow was pressed");
                    break;

                case ConsoleKey.Escape:
                    break;

                default:
                    if (Console.CapsLock && Console.NumberLock)
                    {
                        Console.WriteLine(key.KeyChar);
                    }
                    break;
            }
        }
    }
}

internal class ConsoleBusyIndicator
{
    int _currentBusySymbol;

    public char[] BusySymbols { get; set; }

    public ConsoleBusyIndicator()
    {
        BusySymbols = new[] { '|', '/', '-', '\\' };
    }
    public void UpdateProgress()
    {
        while (true)
        {
            Thread.Sleep(100);
            var originalX = Console.CursorLeft;
            var originalY = Console.CursorTop;

            Console.Write(BusySymbols[_currentBusySymbol]);

            _currentBusySymbol++;

            if (_currentBusySymbol == BusySymbols.Length)
            {
                _currentBusySymbol = 0;
            }

            Console.SetCursorPosition(originalX, originalY);
        }
    }
alhpe
источник
2
Можете ли вы объяснить немного о подходе, который вы использовали, я пытаюсь сделать то же самое с моим консольным приложением. Объяснение кода было бы здорово.
Nayef Harb
@nayefharb устанавливает кучу задач, запускает их, и WaitAll будет ждать, пока они все не вернутся. Следовательно, отдельные задачи не вернутся и будут продолжаться до тех пор, пока не будет запущен CancelKeyPress.
wonea
Console.CursorVisible = false;будет лучше избегать миганий курсора. Добавьте эту строку в качестве первой строки в static void Main(string[] args)блоке.
Hitesh
12

Вот подход для вас сделать что-то в другом потоке и начать слушать нажатие клавиши в другом потоке. И Консоль остановит свою обработку, когда ваш фактический процесс завершится или пользователь завершит процесс, нажав Escклавишу.

class SplitAnalyser
{
    public static bool stopProcessor = false;
    public static bool Terminate = false;

    static void Main(string[] args)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("Split Analyser starts");
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("Press Esc to quit.....");
        Thread MainThread = new Thread(new ThreadStart(startProcess));
        Thread ConsoleKeyListener = new Thread(new ThreadStart(ListerKeyBoardEvent));
        MainThread.Name = "Processor";
        ConsoleKeyListener.Name = "KeyListener";
        MainThread.Start();
        ConsoleKeyListener.Start();

        while (true) 
        {
            if (Terminate)
            {
                Console.WriteLine("Terminating Process...");
                MainThread.Abort();
                ConsoleKeyListener.Abort();
                Thread.Sleep(2000);
                Thread.CurrentThread.Abort();
                return;
            }

            if (stopProcessor)
            {
                Console.WriteLine("Ending Process...");
                MainThread.Abort();
                ConsoleKeyListener.Abort();
                Thread.Sleep(2000);
                Thread.CurrentThread.Abort();
                return;
            }
        } 
    }

    public static void ListerKeyBoardEvent()
    {
        do
        {
            if (Console.ReadKey(true).Key == ConsoleKey.Escape)
            {
                Terminate = true;
            }
        } while (true); 
    }

    public static void startProcess()
    {
        int i = 0;
        while (true)
        {
            if (!stopProcessor && !Terminate)
            {
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("Processing...." + i++);
                Thread.Sleep(3000);
            }
            if(i==10)
                stopProcessor = true;

        }
    }

}
Вахид
источник
5

Если вы используете Visual Studio, вы можете использовать «Пуск без отладки» в меню «Отладка».

Будет автоматически написано «Нажмите любую клавишу для продолжения ...» к консоли для вас после завершения приложения, и она оставит консоль открытой для вас, пока не будет нажата клавиша.

LVBen
источник
3

Рассматривая случаи, которые некоторые другие ответы не обрабатывают хорошо:

  • Отзывчиво : прямое выполнение кода обработки нажатия клавиш; избегает капризов опроса или задержки блокировки
  • Факультативность : глобальное нажатие является неавтоматического ; в противном случае приложение должно выйти нормально
  • Разделение проблем : менее инвазивный код прослушивания; работает независимо от нормального кода консольного приложения.

Многие из решений на этой странице включают опрос Console.KeyAvailableили блокировку Console.ReadKey. Хотя это правда, что .NET Consoleздесь не очень-то сотрудничает, вы можете использовать его Task.Runдля перехода к более современному Asyncрежиму прослушивания.

Основная проблема, о которой следует помнить, заключается в том, что по умолчанию ваш консольный поток не настроен на Asyncработу - это означает, что когда вы выпадаете из нижней части своей mainфункции, а не Asyncожидаете завершения, ваш AppDoman и процесс завершатся. , Правильный способ решения этой проблемы - использовать AsyncContext Стивена Клири для полной Asyncподдержки вашей однопоточной консольной программы. Но для более простых случаев, таких как ожидание нажатия клавиши, установка полного батута может быть излишней.

Пример ниже будет для консольной программы, используемой в некотором итеративном пакетном файле. В этом случае, когда программа завершает свою работу, обычно она должна выходить без нажатия клавиши, а затем мы разрешаем дополнительное нажатие клавиши, чтобы предотвратить выход приложения. Мы можем приостановить цикл, чтобы исследовать вещи, возможно, возобновить, или использовать паузу в качестве известной «контрольной точки», в которой можно аккуратно вырваться из пакетного файла.

static void Main(String[] args)
{
    Console.WriteLine("Press any key to prevent exit...");
    var tHold = Task.Run(() => Console.ReadKey(true));

    // ... do your console app activity ...

    if (tHold.IsCompleted)
    {
#if false   // For the 'hold' state, you can simply halt forever...
        Console.WriteLine("Holding.");
        Thread.Sleep(Timeout.Infinite);
#else                            // ...or allow continuing to exit
        while (Console.KeyAvailable)
            Console.ReadKey(true);     // flush/consume any extras
        Console.WriteLine("Holding. Press 'Esc' to exit.");
        while (Console.ReadKey(true).Key != ConsoleKey.Escape)
            ;
#endif
    }
}
Гленн Слэйден
источник
1

с помощью приведенного ниже кода вы можете прослушивать нажатие клавиши SpaceBar в середине выполнения консоли и делать паузу, пока не будет нажата другая клавиша, а также прослушивать EscapeKey для прерывания основного цикла

static ConsoleKeyInfo cki = new ConsoleKeyInfo();


while(true) {
      if (WaitOrBreak()) break;
      //your main code
}

 private static bool WaitOrBreak(){
        if (Console.KeyAvailable) cki = Console.ReadKey(true);
        if (cki.Key == ConsoleKey.Spacebar)
        {
            Console.Write("waiting..");
            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(250);Console.Write(".");
            }
            Console.WriteLine();
            Console.ReadKey(true);
            cki = new ConsoleKeyInfo();
        }
        if (cki.Key == ConsoleKey.Escape) return true;
        return false;
    }
Иман
источник
-1

Согласно моему опыту, в консольных приложениях проще всего прочитать последнюю нажатую клавишу следующим образом (пример со стрелками):

ConsoleKey readKey = Console.ReadKey ().Key;
if (readKey == ConsoleKey.LeftArrow) {
    <Method1> ();  //Do something
} else if (readKey == ConsoleKey.RightArrow) {
    <Method2> ();  //Do something
}

Я использую, чтобы избежать циклов, вместо этого я пишу код выше в методе, и я вызываю его в конце обоих «Method1» и «Method2», поэтому после выполнения «Method1» или «Method2», Console.ReadKey () .Key готов снова прочитать ключи.

Dhemmlerf
источник