Jak mogę zaktualizować bieżącą linię w aplikacji konsoli C# Windows?

Czy podczas budowania aplikacji konsoli Windows w C# możliwe jest zapisanie do konsoli bez konieczności przedłużania bieżącej linii lub przechodzenia do nowej linii? Na przykład, jeśli chcę pokazać procent reprezentujący jak bliski jest zakończenie procesu, chciałbym po prostu zaktualizować wartość w tej samej linii co kursor i nie muszę umieszczać każdego procentu w nowej linii.

Czy można to zrobić za pomocą "standardowej" aplikacji konsoli C#?

Author: GEOCHET, 2009-05-20

14 answers

Jeśli wydrukujesz tylko "\r" do konsoli, kursor powróci do początku bieżącego wiersza i możesz go przepisać. To powinno wystarczyć:

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

Zwróć uwagę na kilka spacji po numerze, aby upewnić się, że to, co było wcześniej, zostanie usunięte.
Zwróć również uwagę na użycie Write() zamiast WriteLine(), ponieważ nie chcesz dodawać "\n " na końcu linii.

 646
Author: shoosh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-15 15:31:38

Możesz użyć Console.SetCursorPosition, aby ustawić pozycję kursora, a następnie zapisać w bieżącej pozycji.

Oto przykład pokazujący prosty "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);
    }
}

Zauważ, że musisz upewnić się, że wszystkie istniejące wyjścia zostaną nadpisane nowymi wyjściami lub spacjami.

Update: ponieważ krytykowano, że przykład przesuwa kursor tylko o jeden znak, dodam to dla wyjaśnienia: używając SetCursorPosition można ustawić kursor w dowolnej pozycji w oknie konsoli.

Console.SetCursorPosition(0, Console.CursorTop);

Ustawi kursor na początek bieżącego wiersza (lub możesz użyć Console.CursorLeft = 0 bezpośrednio).

 219
Author: Dirk Vollmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-06 08:08:43

Do tej pory mamy trzy konkurencyjne alternatywy dla tego, jak to zrobić:

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
}

Zawsze używałem Console.CursorLeft = 0, wariacji na temat trzeciej opcji, więc postanowiłem zrobić kilka testów. Oto kod, którego użyłem:

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

Na mojej maszynie otrzymuję następujące wyniki:

  • Backspaces: 25.0 seconds
  • powroty: 28.7 sekund
  • SetCursorPosition: 49.7 Second

DODATKOWO, SetCursorPosition spowodowało zauważalne migotanie tego nie zaobserwowałem przy żadnej z alternatyw. Morał polega więc na tym, aby używać backspaces lub powroty karetki, gdy jest to możliwe , i Dzięki za nauczenie mnie szybszego sposobu, aby to zrobić, więc!


Update: w komentarzach Joel sugeruje, że SetCursorPosition jest stała w odniesieniu do odległości poruszanej, podczas gdy inne metody są liniowe. Dalsze testy potwierdzają, że tak jest, jednak stały czas i wolny jest nadal wolny. W moim testy, pisanie długiego ciągu backspaces do konsoli jest szybsze niż SetCursorPosition do około 60 znaków. Więc backspace jest szybszy do zastąpienia części linii krótszej niż 60 znaków (lub tak), i nie migocze, więc będę stał przy moim początkowym poparciu \B Nad \ r I SetCursorPosition.

 68
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-30 14:20:37

Możesz użyć sekwencji specjalnej \B (backspace), aby utworzyć kopię zapasową określonej liczby znaków w bieżącej linii. To po prostu przesuwa bieżącą lokalizację, nie usuwa znaków.

Na przykład:

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

Tutaj, linia jest linią procentową do zapisu do konsoli. Sztuczka polega na wygenerowaniu odpowiedniej liczby znaków \B dla poprzedniego wyjścia.

Przewaga tego nad podejściem \R jest taka, że jeśli działa nawet jeśli Twój procentowy wynik nie jest na początku linii.

 24
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-20 15:36:51

\r jest używany w tym scenariuszu.
\r oznacza powrót karetki, co oznacza, że kursor wraca do początku linii.
Dlatego windows używa \n\r jako znacznika nowej linii.
\n przesuwa cię w dół linii i \r zwraca Cię na początek linii.

 15
Author: Malfist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-02 16:57:10

Musiałem się pobawić klasą divo. Moja nie jest tak zwięzła, ale nie podobało mi się, że użytkownicy tej klasy muszą pisać własną pętlę while(true). Strzelam dla doświadczenia bardziej takiego:

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

    // Do some work...

    spin.Stop(); 
}

I zdałem sobie z tego sprawę z poniższego kodu. Ponieważ nie chcę, aby moja metoda Start() blokowała, nie chcę, aby użytkownik musiał martwić się o pisanie while(spinFlag) -Jak pętli, I chcę, aby pozwolić wielu spinnerów w tym samym czasie musiałem spawn osobny wątek aby poradzić sobie z wirowaniem. A to oznacza, że kod musi być bardziej skomplikowany.

Poza tym, nie zrobiłem zbyt wiele wielowątkowości, więc możliwe jest (prawdopodobnie nawet), że zostawiłem tam subtelny błąd lub trzy. Ale jak na razie działa całkiem nieźle:

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
Author: Joel Coehoorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-20 19:37:19

Jawne użycie zwrotu Carrage (\r) na początku linii, a nie (pośrednio lub bezpośrednio) użycie nowej linii (\N) na końcu powinno uzyskać to, czego chcesz. Na przykład:

void demoPercentDone() {
    for(int i = 0; i < 100; i++) {
        System.Console.Write( "\rProcessing {0}%...", i );
        System.Threading.Thread.Sleep( 1000 );
    }
    System.Console.WriteLine();    
}
 4
Author: James Hugard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-20 18:41:13

Z Console docs w MSDN:

Możesz rozwiązać ten problem ustawiając autor tekstów.NewLine property of the Właściwość Out lub Error do innej linii / align = "left" / Na przykład C # statement, Console.Błąd.NewLine = "\R\n \ r\n";, ustawia zakończenie linii string dla standardowego wyjścia błędu strumień do dwóch powrotnych karetek i linii sekwencje paszy. Wtedy możesz jawnie wywołuje metodę WriteLine obiektu error output stream, jako w C# oświadczenie, Konsola.Błąd.WriteLine ();

Więc - zrobiłem to:

Console.Out.Newline = String.Empty;

Wtedy jestem w stanie sam kontrolować wyjście;

Console.WriteLine("Starting item 1:");
    Item1();
Console.WriteLine("OK.\nStarting Item2:");
Inny sposób na dotarcie tam.
 2
Author: I Wanna Bea Programmer.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
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
Author: Jose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-14 15:09:26

Oto moje spojrzenie na odpowiedzi soosha i 0xa3. Może aktualizować konsolę za pomocą wiadomości użytkownika podczas aktualizacji spinnera i ma również wskaźnik czasu, który upłynął.

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

Użycie jest coś takiego. program zajęć {

    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
Author: cleftheris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-01 21:13:33

Jeśli chcesz zaktualizować jedną linię, ale informacje są zbyt długie, aby wyświetlać je w jednej linii, może potrzebować nowych linii. Napotkałem ten problem, a poniżej jest jeden sposób, aby go rozwiązać.

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
Author: lisunde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-03 11:29:03

Szukałem tego samego rozwiązania w vb.net znalazłem ten i jest świetny.

Jednak jako @ JohnOdom zasugerował lepszy sposób obsługi przestrzeni pustych, jeśli poprzedni jest większy niż obecny..

I make a function in vb.net i pomyślałem, że ktoś może dostać pomoc ..

Oto Mój kod:

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
Author: Zakir_SZH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-28 13:07:47

Oto kolejny :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
Author: Tom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-10 14:10:37

Metoda SetCursorPosition działa w scenariuszu wielowątkowym, gdzie dwie pozostałe metody nie

 -1
Author: imgen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-12 02:50:53