Jak mogę zaktualizować bieżący wiersz w aplikacji konsoli Windows C #?

507

Czy budując aplikację Windows Console w języku C #, można pisać na konsoli bez konieczności przedłużania bieżącej linii lub przejścia do nowej? Na przykład, jeśli chcę pokazać procent reprezentujący, jak blisko procesu jest zakończenie, chciałbym po prostu zaktualizować wartość w tym samym wierszu co kursor i nie musiałem umieszczać każdego procentu w nowym wierszu.

Czy można to zrobić za pomocą „standardowej” aplikacji na konsolę C #?

IVR Avenger
źródło
Jeśli NAPRAWDĘ interesujesz się fajnymi interfejsami wiersza poleceń, powinieneś sprawdzić curses / ncurses.
Charles Addis,
@CharlesAddis, ale czy curses / ncurses nie działa tylko w C ++?
Xam

Odpowiedzi:

783

Jeśli drukujesz tylko "\r"na konsoli, kursor wraca na początek bieżącego wiersza, a następnie możesz go przepisać. To powinno załatwić sprawę:

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

Zwróć uwagę na kilka spacji po numerze, aby upewnić się, że wszystko, co było wcześniej, zostało usunięte.
Zwróć też uwagę na użycie Write()zamiast, WriteLine()ponieważ nie chcesz dodawać „\ n” na końcu wiersza.

buta
źródło
7
dla (int i = 0; i <= 100; ++ i) przejdzie do 100%
Nicolas Tyler
13
Jak sobie radzić, gdy poprzedni zapis był dłuższy niż nowy zapis? Czy jest jakiś sposób na uzyskanie szerokości konsoli i uzupełnienie linii spacjami?
Drew Chapin,
6
@druciferre Z czubka głowy Mogę wymyślić dwie odpowiedzi na twoje pytanie. Oba wymagają najpierw zapisania bieżącego wyniku jako łańcucha i uzupełnienia go określoną liczbą znaków, takich jak: Console.Write ("\ r {0}", strOutput.PadRight (nPaddingCount, '')); „NPaddingCount” może być liczbą, którą sam ustawisz lub możesz śledzić poprzednie wyjście i ustawić nPaddingCount jako różnicę długości między poprzednim a bieżącym wyjściem plus bieżącą długość wyjściową. Jeśli wartość nPaddingCount jest ujemna, nie będziesz musiał używać PadRight, chyba że wykonasz abs (poprz .len - curr.len).
John Odom
1
@malgm Dobrze zorganizowany kod. Jeśli którykolwiek z tuzinów wątków mógłby napisać na konsoli w dowolnym momencie, spowoduje to kłopoty niezależnie od tego, czy piszesz nowe wiersze, czy nie.
Mark
2
@JohnOdom, musisz tylko zachować poprzednią (niepoddaną padaniu) długość wyjściową, a następnie podać ją jako pierwszy argument PadRight(zapisywanie niepakowanego łańcucha lub długości, oczywiście, oczywiście).
Jesper Matthiesen
254

Możesz użyć, Console.SetCursorPositionaby 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);
    }
}

Pamiętaj, że będziesz musiał nadpisać wszelkie istniejące dane wyjściowe nowymi danymi wyjściowymi lub spacjami.

Aktualizacja: Ponieważ krytykowano, że przykład przesuwa kursor tylko o jeden znak wstecz, dodam to dla wyjaśnienia: Używając SetCursorPositionmożesz ustawić kursor w dowolnej pozycji w oknie konsoli.

Console.SetCursorPosition(0, Console.CursorTop);

ustawi kursor na początku bieżącej linii (lub możesz użyć Console.CursorLeft = 0bezpośrednio).

Dirk Vollmar
źródło
8
Problem można rozwiązać za pomocą \ r, ale użycie SetCursorPosition(lub CursorLeft) pozwala na większą elastyczność, np. Brak pisania na początku wiersza, poruszanie się w oknie itp., Więc jest to bardziej ogólne podejście, które można zastosować np. Do wyjścia niestandardowe paski postępu lub grafika ASCII.
Dirk Vollmar
14
+1 za bycie gadatliwym i przekraczanie wezwania do służby. Dobre rzeczy dzięki.
Copas
1
+1 za pokazanie innego sposobu zrobienia tego. Wszyscy inni pokazali \ r, a jeśli PO po prostu aktualizuje wartość procentową, dzięki temu może po prostu zaktualizować wartość bez konieczności ponownego pisania całej linii. OP nigdy tak naprawdę nie powiedział, że chce przejść na początek linii, po prostu chce zaktualizować coś w tym samym wierszu co kursor.
Andy,
1
Dodatkowa elastyczność SetCursorPosition odbywa się kosztem odrobiny prędkości i zauważalnego migotania kursora, jeśli pętla jest wystarczająco długa, aby użytkownik mógł to zauważyć. Zobacz mój komentarz testowy poniżej.
Kevin
5
Potwierdź również, że długość linii nie powoduje zawinięcia konsoli do następnego wiersza lub możesz mieć problemy z treścią spływającą z okna konsoli.
Mandrake
84

Do tej pory mamy trzy konkurujące ze sobą alternatywy:

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, wariant 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 moim komputerze otrzymuję następujące wyniki:

  • Przestrzenie tylne: 25,0 sekundy
  • Powroty karetki: 28,7 sekundy
  • SetCursorPosition: 49,7 sekundy

Dodatkowo SetCursorPositionspowodowało zauważalne migotanie, którego nie zaobserwowałem dla żadnej z alternatyw. Morał polega na tym, aby w miarę możliwości korzystać z odstępów lub zwrotów karetki , a dzięki za nauczenie mnie szybszego sposobu na to, SO!


Aktualizacja : W komentarzach Joel sugeruje, że SetCursorPosition jest stały w odniesieniu do odległości przesuniętej, podczas gdy inne metody są liniowe. Dalsze testy potwierdzają, że tak jest, jednak stały czas i powolność są wciąż powolne. W moich testach pisanie długiego ciągu odstępów w konsoli jest szybsze niż SetCursorPosition, aż osiągnie około 60 znaków. Tak więc backspace jest szybszy do zastępowania części linii krótszych niż 60 znaków (lub mniej więcej) i nie miga, więc zamierzam podtrzymać moje wstępne poparcie \ b nad \ r i SetCursorPosition.

Kevin
źródło
4
Wydajność omawianej operacji naprawdę nie powinna mieć znaczenia. Wszystko powinno nastąpić zbyt szybko, aby użytkownik mógł to zauważyć. Niepotrzebna mikrooptymalizacja jest zła.
Malfist
@Malfist: W zależności od długości pętli użytkownik może zauważyć lub nie. Jak dodałem w powyższej edycji (zanim zobaczyłem twój komentarz), SetCursorPosition wprowadził migotanie i zajmuje prawie dwa razy więcej czasu niż inne opcje.
Kevin
1
Zgadzam się, że jest to mikrooptymalizacja (uruchomienie jej milion razy i zajęcie 50 sekund to wciąż bardzo mało czasu), +1 za wyniki, i zdecydowanie warto wiedzieć.
Andy,
6
Benchmark jest zasadniczo wadliwy. Możliwe, że czas SetCursorPosition () jest taki sam bez względu na to, jak daleko przesuwa się kursor, podczas gdy inne opcje różnią się w zależności od liczby znaków, które konsola musi przetworzyć.
Joel Coehoorn
1
To bardzo miłe podsumowanie różnych dostępnych opcji. Jednak widzę również migotanie podczas używania \ r. W przypadku \ b oczywiście nie ma migotania, ponieważ tekst poprawki („Counting:”) nie jest przepisywany. Będziesz także migotać, jeśli dodasz dodatkowe \ b i przepiszesz poprawiony tekst, tak jak dzieje się to w przypadku \ b i SetCursorPosition. Odnośnie uwagi Joela: Joel ma w zasadzie rację, jednak nadal będzie przewyższał SetCursorPosition na bardzo długich liniach, ale różnica będzie mniejsza.
Dirk Vollmar
27

Możesz użyć sekwencji ucieczki \ b (backspace), aby wykonać kopię zapasową określonej liczby znaków w bieżącym wierszu. To po prostu przesuwa bieżącą lokalizację, nie usuwa postaci.

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 w konsoli. Sztuką jest wygenerowanie poprawnej liczby znaków \ b dla poprzedniego wyniku.

Ma to tę zaletę nad \ r podejścia jest to, że jeśli działa, nawet jeśli nie jest wyjście procent na początku wiersza.

Sean
źródło
1
+1, okazuje się to najszybszą prezentowaną metodą (patrz mój komentarz testowy poniżej)
Kevin
19

\rjest używany w tych scenariuszach.
\r reprezentuje powrót karetki, co oznacza, że ​​kursor wraca na początek linii.
Dlatego Windows używa \n\rjako nowego znacznika linii.
\nprzesuwa cię w dół linii i \rwraca na początek linii.

Malfist
źródło
22
Tyle że tak naprawdę to \ r \ n.
Joel Mueller
14

Po prostu musiałem grać z klasą divo ConsoleSpinner. Mój nie jest tak zwięzły, ale po prostu nie spodobało mi się, że użytkownicy tej klasy muszą napisać własną while(true)pętlę. Kręcę dla bardziej podobnych wrażeń:

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

    // Do some work...

    spin.Stop(); 
}

I zrozumiałem to za pomocą poniższego kodu. Ponieważ nie chcę, aby moja Start()metoda blokowała się, nie chcę, aby użytkownik musiał się martwić o napisanie while(spinFlag)pętli podobnej do tej, i chcę pozwolić na wiele spinnerów w tym samym czasie, musiałem stworzyć osobny wątek do obsługi spinning. A to oznacza, że ​​kod musi być o wiele bardziej skomplikowany.

Nie zrobiłem też zbyt wielu wątków, więc możliwe jest (prawdopodobnie nawet), że zostawiłem tam subtelny błąd lub trzy. Ale wydaje się, że do tej pory działa całkiem dobrze:

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();
    }
}
Joel Coehoorn
źródło
Niezła modyfikacja, chociaż przykładowy kod nie jest mój. Został pobrany z bloga Brada Abramsa (patrz link w mojej odpowiedzi). Myślę, że został napisany jako prosta próbka demonstrująca SetCursorPosition. Przy okazji jestem zdecydowanie zaskoczony (pozytywnie) rozpoczętą dyskusją na temat tego, co uważałem za zwykłą próbkę. Dlatego uwielbiam tę stronę :-)
Dirk Vollmar
4

Jawne użycie powrotu karetki (\ r) na początku wiersza zamiast (niejawnie lub jawnie) za pomocą nowej linii (\ n) na końcu powinno dać ci 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();    
}
James Hugard
źródło
-1, Pytanie prosi o C #, przepisuję go w C # i zmieniasz go z powrotem na F #
Malfist
Wygląda to raczej na konflikt edytowania niż na zmianę C # z powrotem na F #. Jego zmiana nastąpiła minutę po twojej i skupiła się na sprincie.
Andy,
Dzięki za edycję. Zwykle używam trybu interaktywnego F # do testowania rzeczy i doszedłem do wniosku, że ważnymi częściami były wywołania BCL, które są takie same w języku C #.
James Hugard
3
    public void Update(string data)
    {
        Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
        Console.Write(string.Format("\r{0}", data));
    }
Jose
źródło
1

Z dokumentów konsoli w MSDN:

Możesz rozwiązać ten problem, ustawiając właściwość TextWriter.NewLine właściwości Out lub Error na inny ciąg zakończenia linii. Na przykład instrukcja C #, Console.Error.NewLine = "\ r \ n \ r \ n" ;, ustawia ciąg zakończenia linii dla standardowego strumienia wyjściowego błędu na dwie sekwencje powrotu karetki i kolejność wprowadzania linii. Następnie możesz jawnie wywołać metodę WriteLine obiektu strumienia wyjściowego błędu, jak w instrukcji C #, Console.Error.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:");

Kolejny sposób na dotarcie tam.

I Wanna Bea Programmer.
źródło
Możesz po prostu użyć Console.Write () w tym samym celu, bez ponownego definiowania właściwości NewLine ...
Radosław Gers
1

Działa to, jeśli chcesz, aby generowanie plików wyglądało fajnie.

                int num = 1;
                var spin = new ConsoleSpinner();
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("");
                while (true)
                {
                    spin.Turn();
                    Console.Write("\r{0} Generating Files ", num);
                    num++;
                }

I to jest metoda, którą otrzymałem z odpowiedzi poniżej i zmodyfikowałem

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;
                case 4: Console.Write("\r"); break;
            }
            Thread.Sleep(100);
            Console.SetCursorPosition(23, Console.CursorTop);
        }
    }
E.Lahu
źródło
0

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]);
        }
    }
}
Tomek
źródło
0

Jeśli chcesz zaktualizować jedną linię, ale informacja jest zbyt długa, aby wyświetlać się w jednej linii, może potrzebować kilku nowych linii. Napotkałem ten problem, a poniżej jest jeden ze sposobów rozwiązania tego problemu.

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

    }
}
Lisunde
źródło
0

szukałem tego samego rozwiązania w vb.net i znalazłem to i jest świetne.

jednak jak @JohnOdom sugeruje lepszy sposób obsługi pustych miejsc, jeśli poprzedni jest większy niż bieżący.

Zrobiłem funkcję w vb.net i pomyślałem, że ktoś może uzyskać 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
Zakir_SZH
źródło
Tutaj można korzystać z funkcji VB zmiennej statycznej lokalnym: Static intLastLength As Integer.
Mark Hurd
0

Szukałem tego, aby sprawdzić, czy napisane przeze mnie rozwiązanie można zoptymalizować pod kątem szybkości. Chciałem odliczać czas, a nie tylko aktualizować bieżącą linię. Oto, co wymyśliłem. Może być komuś przydatny

            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("");
Adam Hej
źródło
0

Zainspirowany @ E.Lahu Solution, realizacja postępu paska z procentem.

public class ConsoleSpinner
{
    private int _counter;

    public void Turn(Color color, int max, string prefix = "Completed", string symbol = "■",int position = 0)
    {
        Console.SetCursorPosition(0, position);
        Console.Write($"{prefix} {ComputeSpinner(_counter, max, symbol)}", color);
        _counter = _counter == max ? 0 : _counter + 1;
    }

    public string ComputeSpinner(int nmb, int max, string symbol)
    {
        var spinner = new StringBuilder();
        if (nmb == 0)
            return "\r ";

        spinner.Append($"[{nmb}%] [");
        for (var i = 0; i < max; i++)
        {
            spinner.Append(i < nmb ? symbol : ".");
        }

        spinner.Append("]");
        return spinner.ToString();
    }
}


public static void Main(string[] args)
    {
        var progressBar= new ConsoleSpinner();
        for (int i = 0; i < 1000; i++)
        {
            progressBar.Turn(Color.Aqua,100);
            Thread.Sleep(1000);
        }
    }
waleriana Havaux
źródło
0

Oto moje zdanie na temat odpowiedzi Sosh i 0xA3. Może aktualizować konsolę za pomocą komunikatów użytkownika podczas aktualizacji tarczy i ma również wskaźnik upływu czasu.

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 mniej więcej takie:

class Program
{
    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.");

    }
}
klucz wiolinowy
źródło
-1

SetCursorPositionMetoda działa w scenariuszu wielowątkowości, gdzie dwie inne metody nie

imgen
źródło