Comment puis-je mettre à jour la ligne courante dans une application de Console C# Windows?

lors de la construction D'une application Windows Console en C#, est-il possible d'écrire sur la console sans avoir à prolonger une ligne courante ou aller sur une nouvelle ligne? Par exemple, si je veux montrer un pourcentage représentant à quel point un processus est proche de l'achèvement, 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.

peut-on le faire avec une application" standard " c# console?

424
demandé sur GEOCHET 2009-05-20 19:12:53

14 réponses

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

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

noter les quelques espaces après le numéro pour s'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 d'ajouter un "\n" à la fin de la ligne.

646
répondu shoosh 2016-12-15 15:31:38

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

mise à jour: comme il a été critiqué que l'exemple déplace le curseur seulement en arrière d'un caractère, je vais ajouter ceci pour clarification: en utilisant SetCursorPosition vous pouvez régler le curseur à n'importe quelle position dans la fenêtre de la console.

Console.SetCursorPosition(0, Console.CursorTop);

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

219
répondu Dirk Vollmar 2014-10-06 08:08:43

jusqu'à présent, nous avons trois alternatives concurrentes pour la façon de faire ceci:

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, donc j'ai 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:

  • correction arrière: de 25,0 secondes
  • retour du chariot: 28.7 secondes
  • SetCursorPosition: 49.7 secondes

de plus, SetCursorPosition a provoqué un clignotement perceptible que je n'ai pas observé avec l'une ou l'autre des alternatives. Ainsi , la morale est de utiliser des espaces arrière ou retour de chariot lorsque possible , et merci de m'apprendre une façon plus rapide de le faire, donc!


Update : dans les commentaires, Joel suggère que la position de Setcursor est constante par rapport à la distance parcourue alors que les autres méthodes sont linéaires. D'autres essais confirment que c'est le cas, cependant 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. Donc le backspace est plus rapide pour remplacer des parties de la ligne plus courte que 60 caractères (ou ainsi), et il ne clignote pas, donc je vais maintenir mon approbation initiale de \B au-dessus de \R et SetCursorPosition .

68
répondu Kevin 2018-01-30 14:20:37

vous pouvez utiliser la séquence d'échappement \b (backspace) pour sauvegarder un certain nombre de caractères sur la ligne courante. 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 de pourcentage pour écrire à la console. Le truc est de générer le nombre correct de caractères \b pour le précédent sortie.

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

24
répondu Sean 2009-05-20 15:36:51

\r est utilisé pour ces scénarios.

\r représente un retour de chariot qui signifie que le curseur retourne au début de la ligne.

C'est pourquoi windows utilise \n\r comme nouveau marqueur de ligne.

\n vous déplace le long d'une ligne, et \r vous renvoie au début de la ligne.

15
répondu Malfist 2014-01-02 16:57:10

j'ai juste eu à jouer avec la classe ConsoleSpinner du divo. Le mien est loin d'être aussi concis, mais il ne s'est pas bien assis avec moi que les utilisateurs de cette classe doivent écrire leur propre boucle while(true) . Je vise plutôt une expérience 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 que ma méthode Start() soit bloquée, je ne veux pas que l'utilisateur ait à s'inquiéter d'écrire une boucle de type while(spinFlag) , et je veux autoriser plusieurs fileurs en même temps, j'ai dû lancer un fil séparé pour gérer le filage. Et cela signifie que le code doit être beaucoup plus compliqué.

aussi, je n'ai pas fait beaucoup de multi-threading donc il est possible (probablement même) que j'ai laissé un ou trois bogues subtils là-dedans. Mais il semble fonctionner assez bien 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
répondu Joel Coehoorn 2009-05-20 19:37:19

explicitement en utilisant un retour de Carage (\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
répondu James Hugard 2009-05-20 18:41:13

de la Console docs en MSDN:

, Vous pouvez résoudre ce problème en définissant le TextWriter.Propriété NewLine de la Propriété out ou Error vers une autre ligne chaîne de terminaison. Par exemple, l' C# déclaration de la Console.Erreur.De retour à la ligne = "\r\n\r\n";, ensembles de la fin de ligne chaîne de caractères pour la sortie d'erreur standard flux vers deux Retour de chariot et ligne nourrir les séquences. Ensuite, vous pouvez appeler explicitement la méthode WriteLine de la sortie d'erreur objet de flux de données, comme dans la déclaration C# , Console.Erreur.WriteLine ();

j'ai fait ceci:

Console.Out.Newline = String.Empty;

alors 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
répondu I Wanna Bea Programmer. 2011-06-17 18:45:11
    public void Update(string data)
    {
        Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
        Console.Write(string.Format("\r{0}", data));
    }
2
répondu Jose 2012-09-14 15:09:26

Voici mon point de vue sur les réponses de soosh et 0xA3. Il peut mettre à jour la console avec des messages utilisateur lors de la mise à jour du spinner et dispose également d'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'usage 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
répondu cleftheris 2012-10-01 21:13:33

si vous voulez mettre à jour une ligne, mais que l'information est trop longue à afficher sur une ligne, il peut être nécessaire d'ajouter de nouvelles lignes. J'ai rencontré ce problème, et ci-dessous est une façon de résoudre ce problème.

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
répondu lisunde 2014-02-03 11:29:03

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

cependant comme @JohnOdom a suggéré une meilleure façon de gérer l'espace des blancs si le précédent est plus grand que le courant..

j'ai une fonction dans vb.net et de la pensée de 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
répondu Zakir_SZH 2016-12-28 13:07:47

En voici un autre :d

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Working... ");
        int spinIndex = 0;
        while (true)
        {
            // obfuscate FTW! Let's hope overflow is disabled or testers are impatient
            Console.Write("\b" + @"/-\|"[(spinIndex++) & 3]);
        }
    }
}
-1
répondu Tom 2012-10-10 14:10:37

la méthode SetCursorPosition fonctionne dans le scénario de multi-filetage, où les deux autres méthodes ne

-1
répondu imgen 2013-04-12 02:50:53