Ouvir a tecla pressionada no aplicativo de console .NET

224

Como posso continuar executando meu aplicativo de console até pressionar uma tecla (como Escé pressionado?)

Estou assumindo que está enrolado em um loop while. Não gosto ReadKey, pois bloqueia a operação e pede uma tecla, em vez de apenas continuar e ouvir a pressão da tecla.

Como isso pode ser feito?

karlstackoverflow
fonte

Respostas:

343

Use Console.KeyAvailablepara que você ligue apenas ReadKeyquando souber que não bloqueará:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
Jeff Sternal
fonte
6
mas se você fizer isso em vez de readLine (), perderá o recurso incrível de recuperar o histórico pressionando a tecla "para cima".
precisa saber é o seguinte
3
@ v.oddou Após a última linha, basta adicionar o seu Console.ReadLine()e pronto, não?
Gaffi 16/01
1
Esse loop não consumirá muita CPU / RAM? Se não, como?
Sofia
78

Você pode alterar sua abordagem levemente - use Console.ReadKey()para interromper seu aplicativo, mas faça seu trabalho em um encadeamento em segundo plano:

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

Na myWorker.DoStuff()função, você invocaria outra função em um encadeamento em segundo plano (usando Action<>()ou Func<>()é uma maneira fácil de fazê-lo) e retornará imediatamente.

slugster
fonte
60

O caminho mais curto:

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

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

Console.ReadKey()é uma função de bloqueio, interrompe a execução do programa e aguarda um pressionamento de tecla, mas, graças à verificação Console.KeyAvailableprimeiro, o whileloop não é bloqueado, mas continua executando até que o Escbotão seja pressionado.

Yuliia Ashomok
fonte
Exemplo mais fácil até agora. Obrigado.
Joelc #
18

Da maldição do vídeo Construindo aplicativos do console .NET em C # por Jason Roberts em http://www.pluralsight.com

Poderíamos fazer o seguinte para ter vários processos em execução

  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
fonte
2
Você pode explicar um pouco sobre a abordagem que você teve, estou tentando fazer o mesmo com meu aplicativo de console. Uma explicação para o código seria ótima.
Nayef harb
O @nayefharb configura várias tarefas, inicia-as e o WaitAll espera até que todas retornem. Portanto, as tarefas individuais não retornarão e continuarão para sempre até que um CancelKeyPress seja acionado.
WinRed2
Console.CursorVisible = false;será melhor evitar as piscadas do cursor. Adicione esta linha como primeira linha no static void Main(string[] args)bloco.
Hitesh
12

Aqui está uma abordagem para você fazer algo em um segmento diferente e começar a ouvir a tecla pressionada em um segmento diferente. E o console interromperá o processamento quando o processo real terminar ou o usuário finalizar o processo pressionando a Esctecla.

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;

        }
    }

}
waheed
fonte
5

Se você estiver usando o Visual Studio, poderá usar "Iniciar sem depuração" no menu Debug.

Ele escreverá automaticamente "Pressione qualquer tecla para continuar..." para o console para você após a conclusão do aplicativo e ele deixará o console aberto até que uma tecla seja pressionada.

LVBen
fonte
3

Endereçando casos que algumas das outras respostas não lidam bem:

  • Responsivo : execução direta do código de manipulação de teclas; evita os caprichos de pesquisas ou bloqueios de atrasos
  • Possibilidade de Opção : keypress global é opt-in ; caso contrário, o aplicativo deve sair normalmente
  • Separação de preocupações : código de escuta menos invasivo; opera independentemente do código do aplicativo de console normal.

Muitas das soluções nesta página envolvem pesquisa Console.KeyAvailableou bloqueio Console.ReadKey. Embora seja verdade que o .NET Consolenão seja muito cooperativo aqui, você pode usar Task.Runpara avançar em direção a umAsync modo de escuta .

O principal problema a ser observado é que, por padrão, o encadeamento do console não está configurado para Asyncoperação - o que significa que, quando você fica fora da parte inferior da sua mainfunção, em vez de esperar pela Asyncconclusão, o AppDoman e o processo terminam. . Uma maneira adequada de resolver isso seria usar o AsyncContext de Stephen Cleary para estabelecer Asyncsuporte total ao seu programa de console de thread único. Mas para casos mais simples, como esperar por um pressionamento de tecla, instalar um trampolim completo pode ser um exagero.

O exemplo abaixo seria para um programa de console usado em algum tipo de arquivo em lote iterativo. Nesse caso, quando o programa é concluído, normalmente ele deve sair sem a necessidade de pressionar a tecla e, em seguida, permitimos que a tecla opcional seja pressionada para impedir a saída do aplicativo. Podemos pausar o ciclo para examinar as coisas, possivelmente retomar ou usar a pausa como um 'ponto de controle' conhecido no qual interromper o arquivo em lotes de maneira limpa.

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
    }
}
Glenn Slayden
fonte
1

com o código abaixo, você pode ouvir SpaceBar pressionando, no meio da execução do console e pausar até que outra tecla seja pressionada e também ouvir o EscapeKey para interromper o loop principal

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;
    }
Iman
fonte
-1

De acordo com minha experiência, em aplicativos de console, a maneira mais fácil de ler a última tecla pressionada é a seguinte (exemplo com as setas):

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

Eu uso para evitar loops, em vez disso, escrevo o código acima em um método e o chamo no final de "Method1" e "Method2", portanto, depois de executar "Method1" ou "Method2", Console.ReadKey () .Key está pronto para ler as teclas novamente.

Dhemmlerf
fonte