Ascolta la pressione dei tasti nell'app della console .NET


224

Come posso continuare a eseguire la mia applicazione console fino alla pressione di un tasto (come Escviene premuto?)

Suppongo che sia avvolto in un ciclo while. Non mi piace ReadKeypoiché blocca il funzionamento e richiede un tasto, piuttosto che continuare e ascoltare la pressione del tasto.

Come si può fare?

Risposte:


343

Usalo in Console.KeyAvailablemodo da chiamare solo ReadKeyquando sai che non bloccherà:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);

6
ma se lo fai invece di readLine () perdi la straordinaria funzionalità di richiamare la cronologia premendo il tasto "su".
v.oddou,

3
@ v.oddou Dopo l'ultima riga, aggiungi semplicemente il tuo Console.ReadLine()e sei pronto, no?
Gaffi,

1
Questo loop non consumerà molta CPU / RAM? In caso contrario, come?
Sofia,

78

Puoi modificare leggermente il tuo approccio: utilizza Console.ReadKey()per interrompere l'app, ma fai il tuo lavoro in un thread in background:

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

Nella myWorker.DoStuff()funzione dovresti quindi invocare un'altra funzione su un thread in background (usando Action<>()o Func<>()è un modo semplice per farlo), quindi ritorni immediatamente.


60

Il modo più breve:

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

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

Console.ReadKey()è una funzione di blocco, interrompe l'esecuzione del programma e attende la pressione di un tasto, ma grazie al controllo Console.KeyAvailabledapprima, il whileloop non viene bloccato, ma continua fino a quando non Escviene premuto.


L'esempio più semplice finora. Grazie.
Joelc,

18

Dal video maledizione Creazione di applicazioni .NET Console in C # di Jason Roberts su http://www.pluralsight.com

Potremmo fare quanto segue per avere più processi in esecuzione

  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
Puoi spiegarci un po 'l'approccio che stavo cercando di fare lo stesso con la mia applicazione console. Una spiegazione per il codice sarebbe ottima.
nayef harb,

@nayefharb imposta una serie di attività, avviali e WaitAll attenderà fino al loro ritorno. Quindi le singole attività non torneranno e continueranno per sempre fino a quando viene attivato un CancelKeyPress.
vinto il

Console.CursorVisible = false;sarà meglio evitare che il cursore lampeggi glitch. Aggiungi questa riga come prima riga nel static void Main(string[] args)blocco.
Hitesh,

12

Ecco un approccio per fare qualcosa su un thread diverso e iniziare ad ascoltare il tasto premuto in un thread diverso. E la Console interromperà l'elaborazione al termine del processo effettivo o l'utente termina il processo premendo il Esctasto.

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

Se si utilizza Visual Studio, è possibile utilizzare "Avvia senza debug" nel menu Debug.

Scriverà automaticamente "Premere un tasto qualsiasi per continuare ..." alla console per te al completamento dell'applicazione e lascerà la console aperta per te fino a quando non viene premuto un tasto.


3

Affrontare i casi che alcune delle altre risposte non gestiscono bene:

  • Responsive : esecuzione diretta del codice di gestione dei tasti premuti; evita i capricci del polling o il blocco dei ritardi
  • Opzionalità : il keypress globale è opt-in ; altrimenti l'app dovrebbe uscire normalmente
  • Separazione delle preoccupazioni : codice di ascolto meno invasivo; funziona indipendentemente dal normale codice dell'app console.

Molte delle soluzioni in questa pagina prevedono il polling Console.KeyAvailableo il blocco Console.ReadKey. Mentre è vero che .NET Consolenon è molto cooperativo qui, è possibile utilizzare Task.Runper passare a un più modernoAsync modalità di ascolto .

Il problema principale da tenere presente è che, per impostazione predefinita, il thread della console non è impostato per il Asyncfunzionamento, il che significa che, quando si esce dal fondo della mainfunzione, invece di attendere il Asynccompletamento, AppDoman e il processo termineranno . Un modo corretto per risolvere questo problema sarebbe utilizzare AsyncContext di Stephen Cleary per stabilire il pieno Asyncsupporto nel programma della console a thread singolo. Ma per casi più semplici, come l'attesa di un tasto premuto, l'installazione di un trampolino pieno può essere eccessiva.

L'esempio seguente sarebbe per un programma console utilizzato in una sorta di file batch iterativo. In questo caso, quando il programma ha terminato il suo lavoro, normalmente dovrebbe uscire senza richiedere la pressione di un tasto, quindi consentiamo una pressione del tasto opzionale per impedire la chiusura dell'app. Possiamo mettere in pausa il ciclo per esaminare le cose, eventualmente riprendendo, o usare la pausa come un 'punto di controllo' noto in cui uscire in modo pulito dal file batch.

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

con il codice qui sotto puoi ascoltare SpaceBar premendo, nel mezzo dell'esecuzione della tua console e mettere in pausa fino a quando non viene premuto un altro tasto e anche ascoltare EscapeKey per interrompere il ciclo principale

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

Secondo la mia esperienza, nelle app console il modo più semplice per leggere l'ultimo tasto premuto è il seguente (Esempio con i tasti freccia):

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

Uso per evitare i loop, invece scrivo il codice sopra all'interno di un metodo e lo chiamo alla fine di "Metodo1" e "Metodo2", quindi, dopo aver eseguito "Metodo1" o "Metodo2", Console.ReadKey () .Key è pronto a leggere di nuovo i tasti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.