Прослухайте клавішу в додатку консолі .NET


224

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

Я припускаю, що його обернули навколо циклу. Мені не подобається, ReadKeyяк це блокує роботу і просить клавішу, а не просто продовжувати і слухати натискання клавіші.

Як це можна зробити?

Відповіді:


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
Чи не буде цей цикл споживати багато процесора / оперативної пам’яті? Якщо ні, то як?
Софія

78

Ви можете трохи змінити свій підхід - використовуйте 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<>()це простий спосіб зробити це), а потім негайно повертатися.


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 Console Applications в 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);
        }
    }

2
Чи можете ви пояснити трохи про підхід, який у вас був, я намагаюся зробити те ж саме з моєю консольною програмою. Пояснення коду було б чудово.
гайка nayef

@nayefharb налаштуйте купу завдань, запустіть їх, і WaitAll буде чекати, поки всі вони повернуться. Отже, окремі завдання не повернуться, і вони триватимуть назавжди, доки не буде запущено CancelKeyPress.
wonea

Console.CursorVisible = false;найкраще буде уникати глюків миготіння курсору. Додайте цей рядок як перший рядок у static void Main(string[] args)блоці.
Хітеш

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, тоді ви можете скористатися "Пуск без налагодження" в меню налагодження.

Він автоматично напише "Натисніть будь-яку клавішу для продовження." до консолі для вас після завершення програми, і вона залишить консоль відкритою для вас, поки не буде натиснута клавіша.


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 () . Ключ готовий знову прочитати ключі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.