it-swarm.com.de

Wie kann ich die aktuelle Zeile in einer C # Windows Console App aktualisieren?

Ist es beim Erstellen einer Windows-Konsolenanwendung in C # möglich, auf die Konsole zu schreiben, ohne eine aktuelle Zeile erweitern oder eine neue Zeile wählen zu müssen? Wenn ich zum Beispiel einen Prozentsatz anzeigen möchte, der zeigt, wie nahe ein Prozess abgeschlossen ist, möchte ich nur den Wert in derselben Zeile wie der Cursor aktualisieren und muss nicht jeden Prozentsatz in eine neue Zeile einfügen.

Ist dies mit einer "Standard" C # -Konsolen-App möglich?

444
IVR Avenger

Wenn Sie nur "\r" auf die Konsole drucken, kehrt der Cursor zum Anfang der aktuellen Zeile zurück und Sie können ihn neu schreiben. Das sollte den Trick tun:

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

Beachten Sie die wenigen Leerzeichen nach der Nummer, um sicherzustellen, dass alles, was zuvor dort war, gelöscht wird.
Beachten Sie auch die Verwendung von Write() anstelle von WriteLine(), da Sie am Ende der Zeile kein "\ n" hinzufügen möchten.

686
shoosh

Mit Console.SetCursorPosition können Sie die Position des Cursors festlegen und dann an der aktuellen Position schreiben.

Hier ist ein Beispiel , das einen einfachen "Spinner" zeigt:

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

Beachten Sie, dass Sie sicherstellen müssen, dass Sie vorhandene Ausgaben mit neuen Ausgaben oder neuen Leerzeichen überschreiben.

Update: Da kritisiert wurde, dass das Beispiel den Cursor nur um ein Zeichen zurückschiebt, werde ich Folgendes zur Klarstellung hinzufügen: Mit SetCursorPosition können Sie den Cursor auf eine beliebige Position im Konsolenfenster setzen. 

Console.SetCursorPosition(0, Console.CursorTop);

setzt den Cursor an den Anfang der aktuellen Zeile (oder Sie können Console.CursorLeft = 0 direkt verwenden).

227
Dirk Vollmar

Bisher gibt es drei konkurrierende Alternativen, wie das geht:

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
}

Ich habe immer Console.CursorLeft = 0 verwendet, eine Variante der dritten Option, also habe ich mich für einige Tests entschieden. Hier ist der Code, den ich verwendet habe:

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

Auf meiner Maschine erhalte ich folgende Ergebnisse:

  • Backspaces: 25,0 Sekunden
  • Wagenrücklauf: 28,7 Sekunden
  • SetCursorPosition: 49,7 Sekunden

Außerdem verursachte SetCursorPosition ein merkliches Flimmern, das ich bei keiner der Alternativen beobachtet habe. Also ist es die Moral, wenn möglich, Backspaces oder Wagenrückläufe zu verwenden und danke, dass Sie es mir beigebracht haben einen schnelleren Weg, dies zu tun, SO!


Update : In den Kommentaren schlägt Joel vor, dass SetCursorPosition in Bezug auf die zurückgelegte Entfernung konstant ist, während die anderen Methoden linear sind. Weitere Tests bestätigen, dass dies der Fall ist, jedoch konstant und langsam ist immer noch langsam. In meinen Tests ist das Schreiben einer langen Reihe von Backspaces in die Konsole schneller als SetCursorPosition, bis etwa 60 Zeichen vorhanden sind. Der Backspace ist schneller, um Teile der Zeile zu ersetzen, die kürzer als 60 Zeichen sind (oder so), und es flackert nicht, also bleibe ich bei meiner anfänglichen Bestätigung von\b über\r und SetCursorPosition.

71
Kevin

Sie können die Escape-Sequenz\b (backspace) verwenden, um eine bestimmte Anzahl von Zeichen in der aktuellen Zeile zu sichern. Dadurch wird nur die aktuelle Position verschoben, die Zeichen werden nicht entfernt.

Zum Beispiel:

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

Hier ist line die prozentuale Zeile, die in die Konsole geschrieben werden soll. Der Trick besteht darin, die korrekte Anzahl von\b Zeichen für die vorherige Ausgabe zu generieren.

Der Vorteil gegenüber dem Ansatz "\r " besteht darin, dass auch dann funktioniert, wenn sich Ihre prozentuale Ausgabe nicht am Anfang der Zeile befindet.

25
Sean

Für diese Szenarien wird \r verwendet.
\r stellt einen Wagenrücklauf dar, dh der Cursor springt an den Zeilenanfang.
Deshalb verwendet Windows \n\r als neue Linienmarkierung.
\n verschiebt Sie eine Zeile nach unten und \r bringt Sie an den Anfang der Zeile.

16
Malfist

Ich musste nur mit der Klasse ConsoleSpinner des Divos spielen. Meins ist bei weitem nicht so knapp, aber es kam mir einfach nicht gut vor, dass Benutzer dieser Klasse ihre eigene while(true)-Schleife schreiben müssen. Ich suche nach einem Erlebnis, das eher so ist:

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

    // Do some work...

    spin.Stop(); 
}

Und ich habe es mit dem Code unten realisiert. Da meine Start()-Methode nicht blockiert werden soll, möchte ich nicht, dass sich der Benutzer Gedanken über das Schreiben einer while(spinFlag)-like-Schleife machen muss, und ich möchte mehrere Spinner gleichzeitig zulassen, zu denen ich einen separaten Thread erzeugen musste handhaben das Spinnen Das bedeutet, dass der Code viel komplizierter sein muss. 

Ich habe auch noch nicht so viel Multi-Threading gemacht, also ist es möglich (wahrscheinlich sogar), dass ich einen oder drei kleine Fehler hinterlassen habe. Aber es scheint bis jetzt ziemlich gut zu funktionieren:

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

Die Verwendung eines Wagenrücklaufs (\ r) am Anfang der Zeile und nicht (implizit oder explizit) der Verwendung einer neuen Zeile (\ n) am Ende sollte das bekommen, was Sie wollen. Zum Beispiel:

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

Aus den Konsolendokumenten in MSDN:

Sie können dieses Problem lösen, indem Sie die TextWriter.NewLine-Eigenschaft von Out- oder Error-Eigenschaft in eine andere Zeile Abschlusszeichenfolge Zum Beispiel die C # -Anweisung, Console.Error.NewLine = "\ r\n\r\n"; setzt die Leitungsbeendigung Zeichenfolge für die Standardfehlerausgabe Stream zu zwei Wagenrücklauf und Linie Futtersequenzen. Dann kannst du Rufen Sie die WriteLine-Methode explizit auf des Fehlerausgabestromobjekts als in der C # -Anweisung Console.Error.WriteLine ();

Also habe ich das gemacht: 

Console.Out.Newline = String.Empty;

Dann kann ich die Ausgabe selbst steuern.

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

Ein anderer Weg dorthin zu gelangen.

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

Wenn Sie eine Zeile aktualisieren möchten, die Informationen jedoch zu lang sind, um sie in einer Zeile anzuzeigen, benötigen Sie möglicherweise einige neue Zeilen. Ich bin auf dieses Problem gestoßen, und unten ist eine Möglichkeit, dieses Problem zu lösen.

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

ich war auf der Suche nach der gleichen Lösung in vb.net und fand diese und sie ist großartig. 

da @JohnOdom jedoch einen besseren Weg für den Umgang mit Leerzeichen vorschlägt, wenn der vorherige größer ist als der aktuelle.

ich mache eine Funktion in vb.net und dachte, jemand könnte geholfen werden .. 

hier ist mein 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

Ich suchte danach, um zu sehen, ob die Lösung, die ich geschrieben habe, auf Geschwindigkeit optimiert werden könnte. Was ich wollte, war ein Countdown-Timer, nicht nur das Aktualisieren der aktuellen Zeile ... Hier ist, was ich mir ausgedacht habe. Könnte jemandem nützlich sein

            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

Hier sind meine Antworten zu s sooshs und 0xA3s Antworten . Sie können die Konsole mit Benutzermeldungen aktualisieren, während der Spinner aktualisiert wird, und sie haben auch eine abgelaufene Zeitanzeige.

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();
    }
}

nutzung ist so etwas . Klassenprogramm {

    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