640 votes

Comment mettre à jour la ligne en cours dans une application console C# Windows ?

Lors de la création d'une application Windows Console en C#, est-il possible d'écrire dans la console sans devoir prolonger la ligne en cours ou passer à une nouvelle ligne ? Par exemple, si je veux afficher un pourcentage représentant la proximité de l'achèvement d'un processus, j'aimerais simplement mettre à jour la valeur sur la même ligne que le curseur, et ne pas avoir à placer chaque pourcentage sur une nouvelle ligne.

Cela peut-il être fait avec une application console C# "standard" ?

1 votes

Si vous êtes VRAIMENT intéressé par les interfaces de ligne de commande, vous devriez consulter curses/ncurses.

0 votes

@CharlesAddis mais curses/ncurses ne fonctionne-t-il pas uniquement en C++ ?

3 votes

@Xam Alors que je travaillais sur la programmation multiplateforme dans .NET Core, j'ai choisi par hasard la bibliothèque curses comme exemple à mettre en œuvre. Le paquet est dotnet-curses .

957voto

shoosh Points 34322

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

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

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

11 votes

For(int i = 0 ; i <= 100 ; ++i) ira jusqu'à 100%.

19 votes

Comment gérez-vous le cas où l'écriture précédente était plus longue que la nouvelle ? Y a-t-il un moyen d'obtenir la largeur de la console et de remplir la ligne avec des espaces, peut-être ?

9 votes

@druciferre De mémoire, je peux penser à deux réponses à votre question. Elles impliquent toutes deux d'enregistrer d'abord la sortie actuelle sous forme de chaîne de caractères et de la compléter par un nombre défini de caractères, comme ceci : Console.Write(" \r {0}", strOutput.PadRight(nPaddingCount, ' ')) ; Le "nPaddingCount" peut être un nombre que vous définissez vous-même ou vous pouvez garder la trace de la sortie précédente et définir nPaddingCount comme la différence de longueur entre la sortie précédente et la sortie actuelle plus la longueur de la sortie actuelle. Si nPaddingCount est négatif, vous n'aurez pas à utiliser PadRight, sauf si vous faites abs(prev.len - curr.len).

323voto

0xA3 Points 73439

Vous pouvez utiliser Console.SetCursorPosition pour définir la position du curseur, puis écrire à 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 par une nouvelle sortie ou des blancs.

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

Console.SetCursorPosition(0, Console.CursorTop);

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

9 votes

Le problème peut être résolu en utilisant \r mais en utilisant SetCursorPosition (o CursorLeft ) permet plus de flexibilité, par exemple en n'écrivant pas au début de la ligne, en se déplaçant vers le haut de la fenêtre, etc. Il s'agit donc d'une approche plus générale qui peut être utilisée, par exemple, pour produire des barres de progression personnalisées ou des graphiques ASCII.

0 votes

Trop compliqué, oui. Plus de flexibilité, oui. Réponse à la question, pas exactement. Une grande ressource cependant pour, comme vous le dites, une barre de progression personnalisée, etc...

15 votes

+1 pour être verbeux et aller au-delà de l'appel du devoir. Bon travail, merci.

107voto

Kevin Points 3692

Jusqu'à présent, nous disposons de trois solutions concurrentes pour y parvenir :

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 variation de la troisième option, j'ai donc décidé de faire quelques 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

En outre, SetCursorPosition a causé un scintillement perceptible que je n'ai pas observé avec l'une ou l'autre des alternatives. Donc, la morale est de utilisez des retours arrière ou des retours de chariot lorsque cela est possible y merci de m'avoir appris un moyen plus rapide de le faire, SO !


Mise à 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. Des tests supplémentaires confirment que c'est le cas, cependant temps constant et lent est toujours lent. Dans mes tests, écrire une longue chaîne de retours arrière dans la console est plus rapide que SetCursorPosition jusqu'à environ 60 caractères. Ainsi, l'espacement arrière est plus rapide pour remplacer les portions de ligne plus courtes que 60 caractères (environ), y il ne scintille pas, donc je vais rester sur mon approbation initiale de \b sur \r y SetCursorPosition .

4 votes

L'efficacité de l'opération en question ne devrait pas avoir d'importance. Tout devrait se passer trop vite pour que l'utilisateur le remarque. La microptimisation inutile est mauvaise.

0 votes

@Malfist : Selon la longueur de la boucle, l'utilisateur peut ou non le remarquer. Comme je l'ai ajouté dans la modification ci-dessus (avant de voir votre commentaire), SetCursorPosition introduit un scintillement et prend presque deux fois plus de temps que les autres options.

1 votes

Je suis d'accord sur le fait qu'il s'agit d'une micro-optimisation (l'exécuter un million de fois et prendre 50 secondes est toujours une très petite quantité de temps), +1 pour les résultats, et il pourrait certainement être très utile de le savoir.

31voto

Sean Points 22088

Vous pouvez utiliser le \b (backspace) pour sauvegarder un nombre particulier de caractères sur la ligne actuelle. Cette opération ne fait que déplacer l'emplacement actuel, elle 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 de pourcentage à écrire dans la console. L'astuce consiste à générer le nombre correct de \b caractères pour la sortie précédente.

L'avantage de cette méthode par rapport à la \r est que cela fonctionne même si votre pourcentage de sortie n'est pas au début de la ligne.

1 votes

+1, cette méthode s'avère être la plus rapide présentée (voir mon commentaire de test ci-dessous)

21voto

Malfist Points 10488

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

29 votes

Sauf que c'est en fait \r\n.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X