Question Comment puis-je mettre à jour la ligne actuelle dans une application de console Windows C #?


Lors de la construction d'une application Windows Console en C #, est-il possible d'écrire sur la console sans avoir à étendre une ligne en cours ou à passer à une nouvelle ligne? Par exemple, si je veux afficher un pourcentage représentant la fin d'un processus, je voudrais juste mettre à jour la valeur sur la même ligne que le curseur, et ne pas avoir à mettre chaque pourcentage sur une nouvelle ligne.

Est-ce que cela peut être fait avec une application de console C # standard?


416
2018-05-20 15:12


origine


Réponses:


Si vous imprimez seulement "\r" à la console, le curseur revient au début de la ligne en cours, puis vous pouvez le réécrire. Cela devrait faire l'affaire:

for(int i = 0; i < 100; ++i)
{
    Console.Write("\r{0}%   ", i);
}

Remarquez les quelques espaces après le nombre pour vous assurer que tout ce qui était là avant est effacé.
Notez également l'utilisation de Write() au lieu de WriteLine() puisque vous ne voulez pas ajouter un "\ n" à la fin de la ligne.


635
2018-05-20 15:18



Vous pouvez utiliser Console.SetCursorPosition pour définir la position du curseur et écrire ensuite à la position actuelle.

Voici un Exemple montrant un simple "spinner":

static void Main(string[] args)
{
    var spin = new ConsoleSpinner();
    Console.Write("Working....");
    while (true) 
    {
        spin.Turn();
    }
}

public class ConsoleSpinner
{
    int counter;

    public void Turn()
    {
        counter++;        
        switch (counter % 4)
        {
            case 0: Console.Write("/"); counter = 0; break;
            case 1: Console.Write("-"); break;
            case 2: Console.Write("\\"); break;
            case 3: Console.Write("|"); break;
        }
        Thread.Sleep(100);
        Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
    }
}

Notez que vous devrez vous assurer d'écraser toute sortie existante avec une nouvelle sortie ou des blancs.

Mise à jour: Comme il a été critiqué que l'exemple ne déplace le curseur que d'un caractère, je vais ajouter ceci pour plus de précisions: SetCursorPosition vous pouvez placer le curseur sur n'importe quelle position dans la fenêtre de la console.

Console.SetCursorPosition(0, Console.CursorTop);

positionnera le curseur au début de la ligne en cours (ou vous pouvez utiliser Console.CursorLeft = 0 directement).


218
2018-05-20 15:17



Jusqu'à présent, nous avons trois alternatives concurrentes pour ce faire:

Console.Write("\r{0}   ", value);                      // Option 1: carriage return
Console.Write("\b\b\b\b\b{0}", value);                 // Option 2: backspace
{                                                      // Option 3 in two parts:
    Console.SetCursorPosition(0, Console.CursorTop);   // - Move cursor
    Console.Write(value);                              // - Rewrite
}

J'ai toujours utilisé Console.CursorLeft = 0, une variante de la troisième option, alors j'ai décidé de faire des tests. Voici le code que j'ai utilisé:

public static void CursorTest()
{
    int testsize = 1000000;

    Console.WriteLine("Testing cursor position");
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < testsize; i++)
    {
        Console.Write("\rCounting: {0}     ", i);
    }
    sw.Stop();
    Console.WriteLine("\nTime using \\r: {0}", sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    int top = Console.CursorTop;
    for (int i = 0; i < testsize; i++)
    {
        Console.SetCursorPosition(0, top);        
        Console.Write("Counting: {0}     ", i);
    }
    sw.Stop();
    Console.WriteLine("\nTime using CursorLeft: {0}", sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    Console.Write("Counting:          ");
    for (int i = 0; i < testsize; i++)
    {        
        Console.Write("\b\b\b\b\b\b\b\b{0,8}", i);
    }

    sw.Stop();
    Console.WriteLine("\nTime using \\b: {0}", sw.ElapsedMilliseconds);
}

Sur ma machine, j'obtiens les résultats suivants:

  • Backspaces: 25,0 secondes
  • Retour de chariot: 28,7 secondes
  • SetCursorPosition: 49,7 secondes

Aditionellement, SetCursorPosition a causé un scintillement notable que je n'ai pas observé avec l'une ou l'autre des alternatives. Donc, la morale est de utiliser des backspaces ou des retours chariot dans la mesure du possible, et merci de m'avoir appris un moyen plus rapide de le faire, SO!


Mettre à jour: Dans les commentaires, Joel suggère que SetCursorPosition est constant par rapport à la distance déplacée alors que les autres méthodes sont linéaires. D'autres tests confirment que c'est le cas, toutefois temps constant et lent est encore lent. Dans mes tests, écrire une longue chaîne de backspaces à la console est plus rapide que SetCursorPosition jusqu'à environ 60 caractères. Ainsi, le retour arrière est plus rapide pour remplacer des parties de la ligne plus courtes que 60 caractères (ou plus), et il ne scintille pas, donc je vais me tenir à mon approbation initiale de \ b over \ r et SetCursorPosition.


68
2018-05-20 15:58



Vous pouvez utiliser le \ b (backspace) séquence d'échappement pour sauvegarder un nombre particulier de caractères sur la ligne en cours. Cela déplace simplement l'emplacement actuel, il ne supprime pas les caractères.

Par exemple:

string line="";

for(int i=0; i<100; i++)
{
    string backup=new string('\b',line.Length);
    Console.Write(backup);
    line=string.Format("{0}%",i);
    Console.Write(line);
}

Ici, ligne est la ligne en pourcentage à écrire sur la console. L'astuce consiste à générer le nombre correct de \ b caractères pour la sortie précédente.

L'avantage de cela sur le \ r L'approche est que si fonctionne même si votre pourcentage de sortie n'est pas au début de la ligne.


24
2018-05-20 15:36



\r est utilisé pour ces scénarios.
\r  représente un retour chariot qui signifie que le curseur revient au début de la ligne.
C'est pourquoi Windows utilise \n\r comme c'est le nouveau marqueur de ligne.
\n vous déplace vers le bas d'une ligne, et \r vous ramène au début de la ligne.


15
2018-05-20 15:21



Je devais jouer avec le divo ConsoleSpinner classe. Le mien est loin d'être aussi concis, mais ça ne me convenait pas que les utilisateurs de cette classe doivent écrire leur propre while(true) boucle. Je tourne pour une expérience plus comme celle-ci:

static void Main(string[] args)
{
    Console.Write("Working....");
    ConsoleSpinner spin = new ConsoleSpinner();
    spin.Start();

    // Do some work...

    spin.Stop(); 
}

Et je l'ai réalisé avec le code ci-dessous. Puisque je ne veux pas de mon Start() méthode à bloquer, je ne veux pas que l'utilisateur ait à se soucier d'écrire un while(spinFlag) -like loop, et je veux autoriser plusieurs spinners en même temps, j'ai dû engendrer un thread séparé pour gérer le spinning. Et cela signifie que le code doit être beaucoup plus compliqué.

De plus, je n'ai pas fait beaucoup de multi-threading donc il est possible (même probable) que j'ai laissé un bug subtil ou trois là dedans. Mais il semble que ça marche plutôt bien:

public class ConsoleSpinner : IDisposable
{       
    public ConsoleSpinner()
    {
        CursorLeft = Console.CursorLeft;
        CursorTop = Console.CursorTop;  
    }

    public ConsoleSpinner(bool start)
        : this()
    {
        if (start) Start();
    }

    public void Start()
    {
        // prevent two conflicting Start() calls ot the same instance
        lock (instanceLocker) 
        {
            if (!running )
            {
                running = true;
                turner = new Thread(Turn);
                turner.Start();
            }
        }
    }

    public void StartHere()
    {
        SetPosition();
        Start();
    }

    public void Stop()
    {
        lock (instanceLocker)
        {
            if (!running) return;

            running = false;
            if (! turner.Join(250))
                turner.Abort();
        }
    }

    public void SetPosition()
    {
        SetPosition(Console.CursorLeft, Console.CursorTop);
    }

    public void SetPosition(int left, int top)
    {
        bool wasRunning;
        //prevent other start/stops during move
        lock (instanceLocker)
        {
            wasRunning = running;
            Stop();

            CursorLeft = left;
            CursorTop = top;

            if (wasRunning) Start();
        } 
    }

    public bool IsSpinning { get { return running;} }

    /* ---  PRIVATE --- */

    private int counter=-1;
    private Thread turner; 
    private bool running = false;
    private int rate = 100;
    private int CursorLeft;
    private int CursorTop;
    private Object instanceLocker = new Object();
    private static Object console = new Object();

    private void Turn()
    {
        while (running)
        {
            counter++;

            // prevent two instances from overlapping cursor position updates
            // weird things can still happen if the main ui thread moves the cursor during an update and context switch
            lock (console)
            {                  
                int OldLeft = Console.CursorLeft;
                int OldTop = Console.CursorTop;
                Console.SetCursorPosition(CursorLeft, CursorTop);

                switch (counter)
                {
                    case 0: Console.Write("/"); break;
                    case 1: Console.Write("-"); break;
                    case 2: Console.Write("\\"); break;
                    case 3: Console.Write("|"); counter = -1; break;
                }
                Console.SetCursorPosition(OldLeft, OldTop);
            }

            Thread.Sleep(rate);
        }
        lock (console)
        {   // clean up
            int OldLeft = Console.CursorLeft;
            int OldTop = Console.CursorTop;
            Console.SetCursorPosition(CursorLeft, CursorTop);
            Console.Write(' ');
            Console.SetCursorPosition(OldLeft, OldTop);
        }
    }

    public void Dispose()
    {
        Stop();
    }
}

12
2018-05-20 17:42



Explicitement en utilisant un retour chariot (\ r) au début de la ligne plutôt que (implicitement ou explicitement) en utilisant une nouvelle ligne (\ n) à la fin devrait obtenir ce que vous voulez. Par exemple:

void demoPercentDone() {
    for(int i = 0; i < 100; i++) {
        System.Console.Write( "\rProcessing {0}%...", i );
        System.Threading.Thread.Sleep( 1000 );
    }
    System.Console.WriteLine();    
}

4
2018-05-20 15:22



À partir des documents de la console dans MSDN:

Vous pouvez résoudre ce problème en définissant   la propriété TextWriter.NewLine du   Propriété Out ou Error sur une autre ligne   chaîne de terminaison. Par exemple, le   Instruction C #, Console.Error.NewLine =   "\ r \ n \ r \ n" ;, définit la terminaison de ligne   chaîne pour la sortie d'erreur standard   flux à deux retour chariot et ligne   séquences d'alimentation. Ensuite vous pouvez   appeler explicitement la méthode WriteLine   de l'objet de flux de sortie d'erreur, comme   dans la déclaration C #,   Console.Error.WriteLine ();

Donc, j'ai fait ceci:

Console.Out.Newline = String.Empty;

Ensuite, je suis capable de contrôler la sortie moi-même;

Console.WriteLine("Starting item 1:");
    Item1();
Console.WriteLine("OK.\nStarting Item2:");

Une autre façon d'y arriver.


2
2018-06-16 20:53