web-dev-qa-db-fra.com

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

Lors de la création d'une application de console Windows en C #, est-il possible d'écrire sur la console sans avoir à prolonger une ligne actuelle ou à passer à une nouvelle ligne? Par exemple, si je souhaite afficher un pourcentage indiquant la fin d'un processus, je souhaite simplement mettre à jour la valeur sur la même ligne que le curseur, sans avoir à mettre chaque pourcentage sur une nouvelle ligne.

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

444
IVR Avenger

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

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

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

686
shoosh

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 de remplacer toute sortie existante par 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 à des fins de clarification: Utilisez SetCursorPosition pour placer le curseur à n'importe quelle position de la fenêtre de la console. 

Console.SetCursorPosition(0, Console.CursorTop);

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

227
Dirk Vollmar

Jusqu'à présent, nous avons trois solutions concurrentes pour savoir comment procéder:

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 seconds
  • Retour chariot: 28.7 secondes
  • SetCursorPosition: 49.7 secondes

De plus, SetCursorPosition a provoqué un scintillement perceptible que je n’ai observé avec aucune des solutions de rechange. Donc, la morale est de utiliser des espaces de retour ou des retours de chariot lorsque cela est possible et merci de m'avoir appris un moyen plus rapide de le faire, SO!


Update : Dans les commentaires, Joel suggère que SetCursorPosition est constante par rapport à la distance parcourue, tandis que les autres méthodes sont linéaires. Des tests supplémentaires confirment que tel est le cas, cependant le temps constant et lent est toujours lent. Dans mes tests, l'écriture d'une longue chaîne de backspaces sur la console est plus rapide que SetCursorPosition jusqu'à environ 60 caractères. Donc, le retour arrière est plus rapide pour remplacer des parties de la ligne de moins de 60 caractères (ou plus), et il ne clignote pas, je vais donc maintenir mon approbation initiale de\b sur\r et SetCursorPosition.

71
Kevin

Vous pouvez utiliser la séquence d'échappement\b (retour arrière) pour sauvegarder un nombre particulier de caractères sur la ligne en cours. Cela ne fait que déplacer l'emplacement actuel, cela 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 sur la console. L'astuce consiste à générer le nombre correct de caractères\b pour la sortie précédente.

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

25
Sean

\r est utilisé pour ces scénarios.
\r représente un retour à la ligne, 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 vers le bas d'une ligne et \r vous ramène au début de la ligne.

16
Malfist

Je viens de jouer avec la classe ConsoleSpinner de la divo. Le mien est loin d'être aussi concis, mais je ne me suis pas senti bien que les utilisateurs de cette classe doivent écrire leur propre boucle while(true). Je vis pour une expérience plus semblable à 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 compris avec le code ci-dessous. Puisque je ne veux pas que ma méthode Start() soit bloquée, je ne veux pas que l'utilisateur ait à s'inquiéter d'écrire une boucle semblable à while(spinFlag), et je veux autoriser plusieurs fileurs à la fois. Je devais créer un thread séparé pour gérer la rotation. Et cela signifie que le code doit être beaucoup plus compliqué. 

De plus, je n'ai pas fait beaucoup de multi-threading, il est donc possible (probablement même) que j'ai laissé un ou plusieurs bugs subtils à l'intérieur. Mais cela semble assez bien fonctionner jusqu'à présent:

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
Joel Coehoorn

Utiliser explicitement un retour chariot (\ r) au début de la ligne plutôt que (implicitement ou explicitement) utiliser une nouvelle ligne (\ n) à la fin devrait donner 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
James Hugard

Dans la documentation de la console dans MSDN:

Vous pouvez résoudre ce problème en définissant la propriété TextWriter.NewLine du fichier 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 fin de la ligne chaîne pour la sortie d'erreur standard flux vers 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, sous la forme dans la déclaration C #, Console.Error.WriteLine ();

Alors, 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.

    public void Update(string data)
    {
        Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
        Console.Write(string.Format("\r{0}", data));
    }
2
Jose

Si vous souhaitez mettre à jour une ligne, mais que les informations sont trop longues pour être affichées sur une ligne, de nouvelles lignes peuvent être nécessaires. J'ai rencontré ce problème, et voici un moyen de le résoudre.

public class DumpOutPutInforInSameLine
{

    //content show in how many lines
    int TotalLine = 0;

    //start cursor line
    int cursorTop = 0;

    // use to set  character number show in one line
    int OneLineCharNum = 75;

    public void DumpInformation(string content)
    {
        OutPutInSameLine(content);
        SetBackSpace();

    }
    static void backspace(int n)
    {
        for (var i = 0; i < n; ++i)
            Console.Write("\b \b");
    }

    public  void SetBackSpace()
    {

        if (TotalLine == 0)
        {
            backspace(OneLineCharNum);
        }
        else
        {
            TotalLine--;
            while (TotalLine >= 0)
            {
                backspace(OneLineCharNum);
                TotalLine--;
                if (TotalLine >= 0)
                {
                    Console.SetCursorPosition(OneLineCharNum, cursorTop + TotalLine);
                }
            }
        }

    }

    private void OutPutInSameLine(string content)
    {
        //Console.WriteLine(TotalNum);

        cursorTop = Console.CursorTop;

        TotalLine = content.Length / OneLineCharNum;

        if (content.Length % OneLineCharNum > 0)
        {
            TotalLine++;

        }

        if (TotalLine == 0)
        {
            Console.Write("{0}", content);

            return;

        }

        int i = 0;
        while (i < TotalLine)
        {
            int cNum = i * OneLineCharNum;
            if (i < TotalLine - 1)
            {
                Console.WriteLine("{0}", content.Substring(cNum, OneLineCharNum));
            }
            else
            {
                Console.Write("{0}", content.Substring(cNum, content.Length - cNum));
            }
            i++;

        }
    }

}
class Program
{
    static void Main(string[] args)
    {

        DumpOutPutInforInSameLine outPutInSameLine = new DumpOutPutInforInSameLine();

        outPutInSameLine.DumpInformation("");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");


        outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");

        //need several lines
        outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");

        outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbb");

    }
}
0
lisunde

je cherchais la même solution sur vb.net et j’ai trouvé celui-ci et c’est génial. 

cependant, comme @JohnOdom a suggéré un meilleur moyen de gérer l’espace des blancs si le précédent est plus grand que le précédent.

je fais une fonction dans vb.net et pensais que quelqu'un pourrait être aidé .. 

voici mon code:

Private Sub sPrintStatus(strTextToPrint As String, Optional boolIsNewLine As Boolean = False)
    REM intLastLength is declared as public variable on global scope like below
    REM intLastLength As Integer
    If boolIsNewLine = True Then
        intLastLength = 0
    End If
    If intLastLength > strTextToPrint.Length Then
        Console.Write(Convert.ToChar(13) & strTextToPrint.PadRight(strTextToPrint.Length + (intLastLength - strTextToPrint.Length), Convert.ToChar(" ")))
    Else
        Console.Write(Convert.ToChar(13) & strTextToPrint)
    End If
    intLastLength = strTextToPrint.Length
End Sub
0
Zakir_SZH

Je cherchais cette possibilité pour voir si la solution que j'ai écrite pourrait être optimisée pour la vitesse. Ce que je voulais, c’était un compte à rebours, pas seulement une mise à jour de la ligne en cours… Voici ce que j’ai trouvé. Pourrait être utile à quelqu'un

            int sleepTime = 5 * 60;    // 5 minutes

            for (int secondsRemaining = sleepTime; secondsRemaining > 0; secondsRemaining --)
            {
                double minutesPrecise = secondsRemaining / 60;
                double minutesRounded = Math.Round(minutesPrecise, 0);
                int seconds = Convert.ToInt32((minutesRounded * 60) - secondsRemaining);
                Console.Write($"\rProcess will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)} ");
                Thread.Sleep(1000);
            }
            Console.WriteLine("");
0
Adam Hey

Voici mon point de vue sur les réponses de soosh et de 0xA3 ..__ Il peut mettre à jour la console avec les messages de l'utilisateur tout en mettant à jour le compteur et possède un indicateur de temps écoulé.

public class ConsoleSpiner : IDisposable
{
    private static readonly string INDICATOR = "/-\\|";
    private static readonly string MASK = "\r{0} {1:c} {2}";
    int counter;
    Timer timer;
    string message;

    public ConsoleSpiner() {
        counter = 0;
        timer = new Timer(200);
        timer.Elapsed += TimerTick;
    }

    public void Start() {
        timer.Start();
    }

    public void Stop() {
        timer.Stop();
        counter = 0;
    }

    public string Message {
        get { return message; }
        set { message = value; }
    }

    private void TimerTick(object sender, ElapsedEventArgs e) {
        Turn();
    }

    private void Turn() {
        counter++;
        var elapsed = TimeSpan.FromMilliseconds(counter * 200);
        Console.Write(MASK, INDICATOR[counter % 4], elapsed, this.Message);
    }

    public void Dispose() {
        Stop();
        timer.Elapsed -= TimerTick;
        this.timer.Dispose();
    }
}

l'utilisation est quelque chose comme ça . programme de classe {

    static void Main(string[] args) {
        using (var spinner = new ConsoleSpiner()) {
            spinner.Start();
            spinner.Message = "About to do some heavy staff :-)"
            DoWork();
            spinner.Message = "Now processing other staff".
            OtherWork();
            spinner.Stop();
        }
        Console.WriteLine("COMPLETED!!!!!\nPress any key to exit.");

    }
0
cleftheris