Écoutez la pression des touches dans l'application de la console .NET

224

Comment puis-je continuer à exécuter mon application console jusqu'à ce qu'une touche Escsoit enfoncée (comme c'est le cas?)

Je suppose que son enroulé autour d'une boucle while. Je n'aime pas ReadKeycar il bloque le fonctionnement et demande une clé, plutôt que de simplement continuer et écouter la pression de la touche.

Comment cela peut-il être fait?

karlstackoverflow
la source

Réponses:

343

Utilisez-le Console.KeyAvailablepour que vous n'appeliez que ReadKeylorsque vous savez qu'il ne bloquera pas:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
Jeff Sternal
la source
6
mais si vous faites cela au lieu de readLine (), vous perdez la fonctionnalité impressionnante d'avoir le rappel de l'historique en appuyant sur la touche "haut".
v.oddou
3
@ v.oddou Après la dernière ligne, ajoutez simplement votre Console.ReadLine()et vous êtes prêt, non?
Gaffi
1
Cette boucle ne consommera-t-elle pas beaucoup de CPU / RAM? Sinon, comment?
Sofia
78

Vous pouvez modifier légèrement votre approche - utilisez Console.ReadKey()pour arrêter votre application, mais faites votre travail dans un fil d'arrière-plan:

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

Dans la myWorker.DoStuff()fonction, vous invoqueriez ensuite une autre fonction sur un thread d'arrière-plan (en utilisant Action<>()ou Func<>()est un moyen facile de le faire), puis revenez immédiatement.

paresseux
la source
60

Le chemin le plus court:

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

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

Console.ReadKey()est une fonction de blocage, elle arrête l'exécution du programme et attend une pression sur la touche, mais grâce à la vérification Console.KeyAvailablepréalable, la whileboucle n'est pas bloquée, mais en cours d'exécution jusqu'à ce que la Esctouche soit enfoncée.

Yuliia Ashomok
la source
Exemple le plus simple à ce jour. Je vous remercie.
joelc
18

Extrait de la malédiction vidéo Building .NET Console Applications in C # par Jason Roberts sur http://www.pluralsight.com

Nous pourrions suivre pour avoir plusieurs processus en cours d'exécution

  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
la source
2
Pouvez-vous expliquer un peu l'approche que vous aviez, j'essaie de faire de même avec mon application console.Une explication du code serait formidable.
nayef harb
@nayefharb configure un tas de tâches, lancez-les et WaitAll attendra qu'elles soient toutes revenues. Par conséquent, les tâches individuelles ne reviendront pas et elles continueront indéfiniment jusqu'à ce qu'un CancelKeyPress soit déclenché.
wonea
Console.CursorVisible = false;sera préférable d'éviter les problèmes de clignotement du curseur. Ajoutez cette ligne comme première ligne du static void Main(string[] args)bloc.
Hitesh
12

Voici une approche pour que vous puissiez faire quelque chose sur un autre thread et commencer à écouter la touche enfoncée dans un autre thread. Et la console arrêtera son traitement lorsque votre processus réel se terminera ou que l'utilisateur mettra fin au processus en appuyant sur la Esctouche.

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
la source
5

Si vous utilisez Visual Studio, vous pouvez utiliser "Démarrer sans débogage" dans le menu Déboguer.

Il écrira automatiquement "Appuyez sur n'importe quelle touche pour continuer ...". à la console pour vous à la fin de l'application et il laissera la console ouverte pour vous jusqu'à ce qu'une touche soit enfoncée.

LVBen
la source
3

Traiter les cas que certaines des autres réponses ne gèrent pas bien:

  • Responsive : exécution directe du code de gestion des touches; évite les caprices des sondages ou des retards de blocage
  • Optionnalité : la touche globale est opt-in ; sinon l'application devrait se fermer normalement
  • Séparation des préoccupations : code d'écoute moins invasif; fonctionne indépendamment du code normal de l'application console.

La plupart des solutions de cette page impliquent l'interrogation Console.KeyAvailableou le blocage Console.ReadKey. S'il est vrai que le .NET Consolen'est pas très coopératif ici, vous pouvez l'utiliser Task.Runpour évoluer vers un Asyncmode d'écoute plus moderne .

Le principal problème à savoir est que, par défaut, votre thread de console n'est pas configuré pour Asyncfonctionner - ce qui signifie que, lorsque vous tombez du bas de votre mainfonction, au lieu d'attendre la Asyncfin, votre AppDoman et votre processus se terminent . Une bonne façon de résoudre ce problème serait d'utiliser AsyncContext de Stephen Cleary pour établir une Asyncprise en charge complète dans votre programme de console à thread unique. Mais pour les cas plus simples, comme l'attente d'une pression sur une touche, l'installation d'un trampoline complet peut être exagéré.

L'exemple ci-dessous serait pour un programme de console utilisé dans une sorte de fichier batch itératif. Dans ce cas, lorsque le programme a terminé son travail, il doit normalement quitter sans nécessiter de pression sur une touche, puis nous autorisons une pression de touche facultative pour empêcher l'application de se fermer. Nous pouvons interrompre le cycle pour examiner les choses, éventuellement reprendre, ou utiliser la pause comme un «point de contrôle» connu pour sortir proprement du fichier de commandes.

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
la source
1

avec le code ci-dessous, vous pouvez écouter la barre d'espace en appuyant sur, au milieu de l'exécution de votre console et faire une pause jusqu'à ce qu'une autre touche soit appuyée et également écouter EscapeKey pour rompre la boucle 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;
    }
Iman
la source
-1

D'après mon expérience, dans les applications console, la façon la plus simple de lire la dernière touche enfoncée est la suivante (exemple avec les touches fléchées):

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

J'utilise pour éviter les boucles, à la place j'écris le code ci-dessus dans une méthode, et je l'appelle à la fin de "Method1" et "Method2", donc, après avoir exécuté "Method1" ou "Method2", Console.ReadKey () .Key est prêt à relire les clés.

Dhemmlerf
la source