web-dev-qa-db-fra.com

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

Comment puis-je continuer à exécuter mon application console jusqu'à ce qu'une touche soit enfoncée Esc est pressé?)

Je suppose que sa boucle est bouclée. Je n'aime pas ReadKey car il bloque les opérations et demande une clé, plutôt que de simplement continuer à écouter et d'appuyer sur la touche.

Comment cela peut-il être fait?

194

Utilisez Console.KeyAvailable pour que vous appeliez seulement ReadKey lorsque vous savez que cela ne bloquera pas:

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

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 fonction myWorker.DoStuff(), vous invoqueriez une autre fonction sur un thread d'arrière-plan (utiliser Action<>() ou Func<>() est un moyen facile de le faire), puis le renvoyer immédiatement.

71
slugster

Le plus court chemin:

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

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

Console.ReadKey() est une fonction bloquante, elle arrête l'exécution du programme et attend que l'utilisateur appuie sur une touche. Cependant, grâce à la vérification de Console.KeyAvailable, la boucle while n'est pas bloquée, mais elle s'exécute jusqu'au Esc est pressé.

49
Yuliia Ashomok

De la malédiction vidéo Construire des applications .NET Console en C # par Jason Roberts à http://www.pluralsight.com

Nous pourrions faire pour avoir plusieurs processus en cours

  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);
        }
    }
16
alhpe

Voici une approche pour que vous fassiez quelque chose sur un autre thread et que vous commenciez à écouter la touche appuyée dans un autre thread. Et la console arrêtera son traitement à la fin de votre processus ou lorsque l'utilisateur arrêtera le processus en appuyant sur Esc clé.

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;

        }
    }

}
12
waheed

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.

3
LVBen

Traiter des cas que certaines des autres réponses ne traitent pas bien:

  • Responsive: exécution directe du code de traitement de la frappe; évite les aléas des délais de vote ou de blocage
  • Optionalité: appui général sur la touche opt-in; sinon l'application devrait quitter normalement
  • Séparation des préoccupations: code d'écoute moins invasif; fonctionne indépendamment du code de l'application de console normale.

La plupart des solutions proposées sur cette page impliquent l'interrogation Console.KeyAvailable ou le blocage sur Console.ReadKey. S'il est vrai que .NET Console n'est pas très coopératif ici, vous pouvez utiliser Task.Run pour passer à un mode d'écoute Async plus moderne. 

Le principal problème à prendre en compte est que, par défaut, votre thread de la console n’est pas configuré pour l’opération Async - ce qui signifie que, lorsque vous tombez en bas de votre fonction main, au lieu d’attendre les Async achèvements, votre AppDoman et processus va se terminer. Une façon appropriée de résoudre ce problème consiste à utiliser AsyncContext de Stephen Cleary pour établir une prise en charge complète de Async dans votre programme de console à un seul thread. Mais pour des cas plus simples, comme l’attente d’une pression sur une touche, l’installation d’un trampoline complet peut être excessive.

L'exemple ci-dessous concerne un programme de console utilisé dans une sorte de fichier de commandes itératif. Dans ce cas, lorsque le programme est terminé avec son travail, il devrait normalement quitter sans nécessitant une pression sur une touche, puis nous autorisons une touche facultative pour empêcher l'application de quitter. Nous pouvons suspendre le cycle pour examiner des 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
    }
}
1
Glenn Slayden

avec le code ci-dessous, vous pouvez écouter en appuyant sur la barre d'espacement, au milieu de l'exécution de la console, mettre en pause jusqu'à ce que vous appuyiez sur une autre touche et écouter EscapeKey pour interrompre 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;
    }
0
Iman Abidi