🔥 Darmowa Roadmapa .NET — kompletny plan nauki do pierwszej pracy. Pobierz i dołącz do Listy VIP →

Pasek postępu w konsoli C# — kolory, spinner i PadRight

Pasek postępu w konsoli C# — kolorowy pasek z kratek i błąd Gotowe!nie po powrocie karetki

Twój program kopiuje pliki. Trwa to dziesięć sekund, a przez te dziesięć sekund na ekranie nic się nie dzieje. Użytkownik nie wie, czy program pracuje, czy się zawiesił — więc zamyka okno. Pasek postępu rozwiązuje ten problem i w konsoli C# zrobisz go w kilkunastu linijkach, bez żadnej biblioteki.

Ten wpis to wersja tekstowa odcinka Program .NET Developer #5 na YouTube. Kod jest ten sam co w filmie, a każdy wynik uruchomiłem na .NET 10. Po drodze program napisze na ekranie Gotowe!nie: 100% — pokażę, skąd się bierze to „nie”, bo ten sam błąd zrobisz przy każdym napisie, który zmienia się w miejscu. Zakładam, że znasz pętle w C#; resztę wyjaśniam po drodze.

Punkt wyjścia: WriteLine i ściana tekstu

Najprostsza wersja: pętla liczy od 0 do 100 i wypisuje każdy procent.

for (int i = 0; i <= 100; i++)
{
    Console.WriteLine($"Kopiowanie: {i}%");
    Thread.Sleep(100);
}

Console.WriteLine("Gotowe!");

Console.WriteLine wypisuje tekst i przechodzi do nowej linii. Thread.Sleep(100) usypia program na 100 milisekund — tu udaje prawdziwą pracę, a 101 obrotów pętli to około 10 sekund. Wynik:

Kopiowanie: 0%
Kopiowanie: 1%
Kopiowanie: 2%
…
Kopiowanie: 100%
Gotowe!

Działa, ale to 102 linie. Każdy procent w osobnym wierszu, a wszystko, co było na ekranie wcześniej, ucieka do góry. Chcemy jednej linii, w której liczba zmienia się w miejscu. Więcej o tym, jak konsola przyjmuje i wypisuje dane, przeczytasz we wpisie o wejściu i wyjściu w C#.

Zasada: WriteLine = tekst + nowa linia. Do napisu, który ma się zmieniać w miejscu, potrzebujesz czegoś innego.

Write i powrót karetki \r — tekst zmienia się w miejscu

Dwie zmiany: Write zamiast WriteLine i znak \r na początku napisu.

for (int i = 0; i <= 100; i++)
{
    Console.Write($"\rKopiowanie: {i}%");
    Thread.Sleep(100);
}

Console.WriteLine("\rGotowe!");

Console.Write wypisuje tekst, ale nie przechodzi do nowej linii — kursor zostaje na końcu napisu. Znak \r (ukośnik i litera r) to jeden znak specjalny: powrót karetki. Nazwa pochodzi z maszyn do pisania, w których karetka wracała na początek wiersza. W konsoli jest tak samo: kursor wraca na początek tej samej linii, więc kolejny procent pisze się na poprzednim. Ten sam znak stoi przed Gotowe!, żeby słowo zaczęło się od początku linii.

Procenty rzeczywiście zmieniają się w miejscu. A na końcu na ekranie zostaje:

Gotowe!nie: 100%

Program skończył pracę i sam sobie zaprzeczył.

Pułapka: powrót karetki nie czyści linii

\r przenosi kursor na początek linii — i nic więcej. Nie kasuje tego, co tam było. Na ekranie stało Kopiowanie: 100%, czyli 16 znaków. Gotowe! ma 7 znaków, więc nadpisało tylko siedem pierwszych liter: Kopiowa. Reszta starego napisu — nie: 100% — została na miejscu.

Sprawdź sam, jak to wygląda znak po znaku, także dla innych napisów:

Konsola znak po znaku

Co zostanie na ekranie po \r?

Nowy napis po końcu pętli
Linia w konsoli (kolumny 0–23)

Wszystkie warianty ze wstawki uruchomiłem na .NET 10 i odtworzyłem tak, jak rysuje je konsola:

Stara liniaNowy napisNa ekranie
Kopiowanie: 100% (16 zn.)"\rGotowe!" (7 zn.)Gotowe!nie: 100%
Kopiowanie: 100%"\rOK" (2 zn.)OKpiowanie: 100%
Kopiowanie: 100%"\rSkopiowano 100 plików" (21 zn.)Skopiowano 100 plików
Kopiowanie: 100%"\r" + "OK".PadRight(50)OK

Problem wychodzi tylko wtedy, gdy nowy napis jest krótszy od starego. Dlatego łatwo go przeoczyć: dopóki teksty rosną (9% → 10% → 100%), wszystko wygląda dobrze.

Naprawa: PadRight dopełnia napis spacjami

for (int i = 0; i <= 100; i++)
{
    Console.Write($"\rKopiowanie: {i}%");
    Thread.Sleep(100);
}

Console.WriteLine("\r" + "Gotowe!".PadRight(50));

PadRight(50) — po polsku „dopełnij z prawej do 50 znaków” — dokleja do napisu tyle spacji, żeby miał razem 50 znaków. Spacja też jest znakiem, więc zamazuje resztki starego tekstu. Na ekranie zostaje samo Gotowe!. Liczba 50 jest z zapasem, bo nasza linia jeszcze się wydłuży. PadRight przydaje się też do wyrównywania kolumn — przykłady znajdziesz we wpisie o generowaniu dokumentów i raportów.

Zasada: \r nie czyści linii. Jeśli nowy tekst jest krótszy od starego, zamaż różnicę spacjami — najprościej przez PadRight.

Pasek z kratek: new string('█', ile)

Teraz prawdziwy pasek z 20 kratek:

Console.OutputEncoding = System.Text.Encoding.UTF8;

for (int i = 0; i <= 100; i++)
{
    int pelne = i / 5;
    string pasek = new string('█', pelne) + new string('░', 20 - pelne);

    Console.Write($"\rKopiowanie [{pasek}] {i}%");
    Thread.Sleep(100);
}

Console.WriteLine("\r" + "Gotowe!".PadRight(50));

Trzy rzeczy w tym kodzie:

  • Console.OutputEncoding = System.Text.Encoding.UTF8; mówi konsoli, w jakim zapisie dostaje znaki. Dzięki temu kratki █ i ░ wyglądają tak samo na każdym komputerze.
  • int pelne = i / 5; — ile kratek jest pełnych. 100 / 5 = 20, czyli pasek pełny; 37 / 5 = 7. To dzielenie liczb całkowitych, więc część po przecinku po prostu odpada (37 / 5 to 7, a nie 7,4). Więcej o tym we wpisie o operatorach w C#.
  • new string('█', pelne) tworzy napis z pelne takich samych znaków. Do niego doklejamy jasne kratki na resztę: 20 - pelne. Razem zawsze 20 znaków, więc pasek ma stałą szerokość.

W trakcie, przy 37%, na ekranie jest:

Kopiowanie [███████░░░░░░░░░░░░░] 37%

Na końcu PadRight(50) zamazuje cały pasek i zostaje Gotowe!.

Kolory: ForegroundColor i ResetColor

Console.OutputEncoding = System.Text.Encoding.UTF8;

for (int i = 0; i <= 100; i++)
{
    int pelne = i / 5;
    string pasek = new string('█', pelne) + new string('░', 20 - pelne);

    if (i < 34)
        Console.ForegroundColor = ConsoleColor.Red;
    else if (i < 67)
        Console.ForegroundColor = ConsoleColor.Yellow;
    else
        Console.ForegroundColor = ConsoleColor.Green;

    Console.Write($"\rKopiowanie [{pasek}] {i}%");
    Console.ResetColor();
    Thread.Sleep(100);
}

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("\r" + "Gotowe!".PadRight(50));
Console.ResetColor();

Console.ForegroundColor to kolor liter, które konsola napisze od tej chwili. Poniżej 34% pasek jest czerwony, poniżej 67% żółty, dalej zielony. Zaraz po wypisaniu linii Console.ResetColor() przywraca kolor konsoli. Bez tego konsola zostałaby kolorowa także po końcu programu — kolor to ustawienie konsoli, a nie jednego napisu.

Zasada: kolor ustawiasz przed Write, a zaraz po nim przywracasz go przez ResetColor.

Spinner i schowany kursor

Ostatnie dwa szlify: kręcący się znak na początku linii i schowany kursor.

Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.CursorVisible = false;

char[] spinner = { '|', '/', '-', '\\' };

for (int i = 0; i <= 100; i++)
{
    int pelne = i / 5;
    string pasek = new string('█', pelne) + new string('░', 20 - pelne);

    if (i < 34)
        Console.ForegroundColor = ConsoleColor.Red;
    else if (i < 67)
        Console.ForegroundColor = ConsoleColor.Yellow;
    else
        Console.ForegroundColor = ConsoleColor.Green;

    Console.Write($"\r{spinner[i % 4]} Kopiowanie [{pasek}] {i}%");
    Console.ResetColor();
    Thread.Sleep(100);
}

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("\r" + "Gotowe!".PadRight(50));
Console.ResetColor();
Console.CursorVisible = true;

Spinner to tablica czterech znaków: |, /, - i \. Ostatni zapisujemy jako '\\' — pojedynczy ukośnik w C# zaczyna znak specjalny (jak \r), więc sam ukośnik trzeba zapisać podwójnie. Który znak pokazać, wybiera i % 4 — reszta z dzielenia przez 4, czyli zawsze 0, 1, 2 albo 3. Znaki idą po kolei w kółko i wygląda to, jakby kreska się kręciła.

Console.CursorVisible = false chowa mrugający kursor, który psułby efekt na końcu paska. Na końcu programu oddajemy go: Console.CursorVisible = true.

Sprawdź sam: pasek dla dowolnego procentu

Przesuń suwak — wstawka liczy linię dokładnie tak jak 06-Spinner.cs: kratki z i / 5, znak spinnera z i % 4 i kolor z progów 34 i 67.

Sprawdź sam

06-Spinner.cs — jedna klatka pętli

int pelne = i / 5; string pasek = new string('█', pelne) + new string('░', 20 - pelne); if (i < 34) Console.ForegroundColor = ConsoleColor.Red; else if (i < 67) Console.ForegroundColor = ConsoleColor.Yellow; else Console.ForegroundColor = ConsoleColor.Green; Console.Write($"\r{spinner[i % 4]} Kopiowanie [{pasek}] {i}%");
pelne = i / 5
20 - pelne
i % 4 → znak
kolor

Pułapki, które wyjdą w praktyce

1. CursorVisible przy przekierowanym wyjściu rzuca wyjątek. Uruchomiłem 06-Spinner.cs z wynikiem zapisywanym do pliku (dotnet run 06-Spinner.cs > wynik.txt):

Unhandled exception. System.IO.IOException: Nieprawidłowe dojście.
   at System.ConsolePal.set_CursorVisible(Boolean value)

Przy przekierowaniu nie ma okna konsoli, którego kursor można schować. Ten sam plik uruchomiony w zwykłym oknie konsoli (albo w Visual Studio przez Ctrl+F5) kończy się poprawnie. Jeśli twój program może działać bez okna — na przykład w zaplanowanym zadaniu albo z wynikiem do pliku — sprawdź najpierw Console.IsOutputRedirected.

2. Kolory też znikają przy przekierowaniu. W pliku z wynikiem 05-Kolory.cs nie ma ani jednego kodu koloru — .NET wysyła kolory tylko do prawdziwej konsoli. To nie błąd, ale nie licz, że kolory zobaczysz w logu.

3. Krótszy tekst po dłuższym. To nasze Gotowe!nie. Dotyczy każdego napisu z \r: licznika, czasu do końca, nazwy kopiowanego pliku. Dopełnij spacjami albo pisz zawsze tak samo długi tekst.

4. Zapomniany ResetColor. Jeśli program skończy się (albo rzuci wyjątek) po ustawieniu koloru, konsola może zostać w tym kolorze. Przywracaj kolor zaraz po wypisaniu.

Ściągawka

CoKodPo co
tekst bez nowej liniiConsole.Write(…)kursor zostaje na końcu napisu
powrót na początek linii"\r…"kolejny tekst pisze się na poprzednim
zamazanie resztek"Gotowe!".PadRight(50)spacje do 50 znaków
napis z jednego znakunew string('█', pelne)kratki paska
kolor literConsole.ForegroundColor = ConsoleColor.Greenod tej chwili zielone
przywrócenie koloruConsole.ResetColor()konsola wraca do swojego koloru
znak w kółkospinner[i % 4]0, 1, 2, 3, 0, 1…
kursorConsole.CursorVisible = falsetylko w prawdziwym oknie konsoli

Zadanie: 40 kratek

Zrób pasek z 40 kratek zamiast 20:

Console.OutputEncoding = System.Text.Encoding.UTF8;

for (int i = 0; i <= 100; i++)
{
    int pelne = i * 40 / 100;
    string pasek = new string('█', pelne) + new string('░', 40 - pelne);

    Console.Write($"\rKopiowanie [{pasek}] {i}%");
    Thread.Sleep(100);
}

Console.WriteLine("\r" + "Gotowe!".PadRight(70));
  1. Uruchom i sprawdź, ile kratek jest pełnych przy 37%.
  2. Celowo zepsuj: usuń .PadRight(70) z ostatniej linii (zostaw "\rGotowe!") i zobacz, co zostanie na ekranie.

Napisz w komentarzu pod filmem, jakie słowo ci wyszło. Odpowiedź jest w przypiętym komentarzu.

Podsumowanie

  • Write + \r zmienia tekst w miejscu: Write nie przechodzi do nowej linii, a \r wraca na jej początek.
  • \r nie czyści linii. Krótszy napis zostawia resztki starego — stąd Gotowe!nie: 100%. Naprawa: PadRight.
  • Pasek to new string('█', pelne) + new string('░', 20 - pelne), gdzie pelne = i / 5.
  • Kolor ustawiasz przed Write i od razu przywracasz przez ResetColor.
  • CursorVisible działa tylko w prawdziwym oknie konsoli — przy przekierowanym wyjściu rzuca IOException.

🎥 Wersja wideo: ten sam przykład krok po kroku, kod pisany na żywo w Visual Studio — Program .NET Developer #5 — Pasek postępu w konsoli
🎓 Więcej takich programów: Program .NET Developer — od podstaw C# do aplikacji webowych, krok po kroku.
📘 Darmowa roadmapa .NET Developera: dev-hobby.pl/lista-vip

Zobacz

Mariusz Jurczenko
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →