Pasek postępu w aplikacji konsolowej

84

Piszę prostą aplikację konsoli C #, która przesyła pliki na serwer SFTP. Jednak liczba plików jest duża. Chciałbym wyświetlić procent przesłanych plików lub tylko liczbę przesłanych plików z łącznej liczby plików do przesłania.

Najpierw otrzymuję wszystkie pliki i całkowitą liczbę plików.

string[] filePath = Directory.GetFiles(path, "*");
totalCount = filePath.Length;

Następnie przeglądam plik w pętli i przesyłam je jeden po drugim w pętli foreach.

foreach(string file in filePath)
{
    string FileName = Path.GetFileName(file);
    //copy the files
    oSftp.Put(LocalDirectory + "/" + FileName, _ftpDirectory + "/" + FileName);
    //Console.WriteLine("Uploading file..." + FileName);
    drawTextProgressBar(0, totalCount);
}

W pętli foreach mam pasek postępu, z którym mam problemy. Nie wyświetla się poprawnie.

private static void drawTextProgressBar(int progress, int total)
{
    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = 32;
    Console.Write("]"); //end
    Console.CursorLeft = 1;
    float onechunk = 30.0f / total;

    //draw filled part
    int position = 1;
    for (int i = 0; i < onechunk * progress; i++)
    {
        Console.BackgroundColor = ConsoleColor.Gray;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw unfilled part
    for (int i = position; i <= 31 ; i++)
    {
        Console.BackgroundColor = ConsoleColor.Green;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw totals
    Console.CursorLeft = 35;
    Console.BackgroundColor = ConsoleColor.Black;
    Console.Write(progress.ToString() + " of " + total.ToString() + "    "); //blanks at the end remove any excess
}

Wynik to tylko [] 0 z 1943 roku

Co ja tu robię źle?

EDYTOWAĆ:

Próbuję wyświetlić pasek postępu podczas ładowania i eksportowania plików XML. Jednak przechodzi przez pętlę. Po zakończeniu pierwszej rundy przechodzi do drugiej i tak dalej.

string[] xmlFilePath = Directory.GetFiles(xmlFullpath, "*.xml");
Console.WriteLine("Loading XML files...");
foreach (string file in xmlFilePath)
{
     for (int i = 0; i < xmlFilePath.Length; i++)
     {
          //ExportXml(file, styleSheet);
          drawTextProgressBar(i, xmlCount);
          count++;
     }
 }

Nigdy nie opuszcza pętli for ... Jakieś sugestie?

smr5
źródło
Co to jest xmlCount i count?
eddie_cat
Policz tylko przyrost. xmlCount to po prostu całkowita liczba plików XML w określonym folderze DirectoryInfo xmlDir = new DirectoryInfo (xmlFullpath); xmlCount = xmlDir.GetFiles (). Length;
smr5
1
Ponadto, dlaczego pętla for znajduje się w pętli foreach? Wydaje się, że powtarza to samo. Prawdopodobnie nie jest konieczne trzymanie pętli foreach.
eddie_cat
1
Czy usunąłeś zewnętrzną z każdej pętli? Zmiana komentowanego bitu naExportXml(xmlFilePath[i])
eddie_cat
1
To było to. Mam tylko pętlę for i działa.
smr5

Odpowiedzi:

11

Ten wiersz jest twoim problemem:

drawTextProgressBar(0, totalCount);

Mówisz, że postęp jest zerowy w każdej iteracji, to powinno być zwiększane. Może zamiast tego użyj pętli for.

for (int i = 0; i < filePath.length; i++)
{
    string FileName = Path.GetFileName(filePath[i]);
    //copy the files
    oSftp.Put(LocalDirectory + "/" + FileName, _ftpDirectory + "/" + FileName);
    //Console.WriteLine("Uploading file..." + FileName);
    drawTextProgressBar(i, totalCount);
}
eddie_cat
źródło
Zadziałało za pierwszym razem i robię to samo w innym miejscu w tym samym czasie i powoduje pętlę. To się nigdy nie kończy. Zaktualizowałem swój post. Czy mógłbyś na to rzucić okiem? Dzięki.
smr5
Co zaktualizowałeś? Dla mnie wygląda tak samo, czego mi brakuje?
eddie_cat
200

Szukałem też paska postępu konsoli. Nie znalazłem takiego, który zrobiłby to, czego potrzebowałem, więc zdecydowałem się na własną. Kliknij tutaj, aby uzyskać kod źródłowy (licencja MIT).

Animowany pasek postępu

Funkcje:

  • Działa z przekierowanym wyjściem

    Jeśli przekierowujesz dane wyjściowe aplikacji konsolowej (np. Program.exe > myfile.txt ), Większość implementacji ulegnie awarii z wyjątkiem. Dzieje się tak, ponieważ Console.CursorLefti Console.SetCursorPosition()nie obsługują przekierowanego wyjścia.

  • Przybory IProgress<double>

    Pozwala to na używanie paska postępu z operacjami asynchronicznymi, które zgłaszają postęp w zakresie [0..1].

  • Bezpieczne dla wątków

  • Szybki

    ConsoleKlasa jest znany ze swojej wydajności fatalne. Zbyt wiele wywołań powoduje spowolnienie aplikacji. Ta klasa wykonuje tylko 8 wywołań na sekundę, bez względu na to, jak często zgłaszasz aktualizację postępu.

Użyj tego w ten sposób:

Console.Write("Performing some task... ");
using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
        progress.Report((double) i / 100);
        Thread.Sleep(20);
    }
}
Console.WriteLine("Done.");
Daniel Wolf
źródło
4
To wygląda całkiem schludnie! Czy rozważyłbyś dodanie do niego licencji OSS, takiej jak MIT? choosealicense.com
Daniel grał
2
Dobry pomysł. Skończone.
Daniel Wolf,
@DanielWolf W jaki sposób uzyskałeś Console.Write ze zmiany CursorPosition?
JJS
1
@knocte: W kodzie produkcyjnym z pewnością tak. Celem było, aby przykład był jak najbardziej zwięzły i nie odwracał uwagi od istotnych części.
Daniel Wolf,
8
GIF jest atrakcyjny.
Lei Yang
18

Wiem, że to stary wątek i przepraszam za autopromocję, jednak niedawno napisałem bibliotekę konsoli open source dostępną w nuget Goblinfactory Konsola z obsługą wielu pasków postępu Threadafe , która może pomóc każdemu nowemu na tej stronie, który potrzebuje takiej, która nie blokuje głównego wątku.

Jest to nieco inne od powyższych odpowiedzi, ponieważ umożliwia równoległe rozpoczęcie pobierania i zadań oraz kontynuowanie innych zadań;

okrzyki, mam nadzieję, że to pomoże

ZA

var t1 = Task.Run(()=> {
   var p = new ProgressBar("downloading music",10);
   ... do stuff
});

var t2 = Task.Run(()=> {
   var p = new ProgressBar("downloading video",10);
   ... do stuff
});

var t3 = Task.Run(()=> {
   var p = new ProgressBar("starting server",10);
   ... do stuff .. calling p.Refresh(n);
});

Task.WaitAll(new [] { t1,t2,t3 }, 20000);
Console.WriteLine("all done.");

daje ten typ wyniku

wprowadź opis obrazu tutaj

Pakiet nuget zawiera również narzędzia do pisania w sekcji okna konsoli z pełną obsługą obcinania i zawijania, a także PrintAt a także różne inne pomocne klasy.

Napisałem pakiet nuget, ponieważ zawsze pisałem wiele typowych procedur konsoli za każdym razem, gdy pisałem skrypty i narzędzia do kompilacji i opsowania konsoli.

Jeśli ściągałem kilka plików, zwykłem powoli przesuwać Console.Writesię do ekranu w każdym wątku i próbowałem różnych sztuczek, aby ułatwić odczytanie przeplatanego wyjścia na ekranie, np. Różne kolory lub liczby. Ostatecznie napisałem bibliotekę okienkową, aby dane wyjściowe z różnych wątków mogły być po prostu drukowane w różnych oknach, co zmniejszyło tonę standardowego kodu w moich skryptach narzędziowych.

Na przykład ten kod,

        var con = new Window(200,50);
        con.WriteLine("starting client server demo");
        var client = new Window(1, 4, 20, 20, ConsoleColor.Gray, ConsoleColor.DarkBlue, con);
        var server = new Window(25, 4, 20, 20, con);
        client.WriteLine("CLIENT");
        client.WriteLine("------");
        server.WriteLine("SERVER");
        server.WriteLine("------");
        client.WriteLine("<-- PUT some long text to show wrapping");
        server.WriteLine(ConsoleColor.DarkYellow, "--> PUT some long text to show wrapping");
        server.WriteLine(ConsoleColor.Red, "<-- 404|Not Found|some long text to show wrapping|");
        client.WriteLine(ConsoleColor.Red, "--> 404|Not Found|some long text to show wrapping|");

        con.WriteLine("starting names demo");
        // let's open a window with a box around it by using Window.Open
        var names = Window.Open(50, 4, 40, 10, "names");
        TestData.MakeNames(40).OrderByDescending(n => n).ToList()
             .ForEach(n => names.WriteLine(n));

        con.WriteLine("starting numbers demo");
        var numbers = Window.Open(50, 15, 40, 10, "numbers", 
              LineThickNess.Double,ConsoleColor.White,ConsoleColor.Blue);
        Enumerable.Range(1,200).ToList()
             .ForEach(i => numbers.WriteLine(i.ToString())); // shows scrolling

produkuje to

wprowadź opis obrazu tutaj

Możesz także tworzyć paski postępu w oknie tak samo łatwo, jak pisanie w oknach. (mieszać i łączyć).

kod śniegu
źródło
To jest po prostu najlepsze
Pratik
9

Możesz spróbować https://www.nuget.org/packages/ShellProgressBar/

Właśnie natknąłem się na tę implementację paska postępu - tak naprawdę jest to platforma wieloplatformowa łatwą w użyciu, dość konfigurowalną i robi to, co powinno od razu po wyjęciu z pudełka.

Udostępnianie, bo bardzo mi się to podobało.

Schweder
źródło
6

Skopiowałem twoją ProgressBarmetodę. Ponieważ twój błąd był w pętli, jak wspomniano w zaakceptowanej odpowiedzi. Ale ProgressBarmetoda ma również pewne błędy składniowe. Oto wersja robocza. Lekko zmieniony.

private static void ProgressBar(int progress, int tot)
{
    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = 32;
    Console.Write("]"); //end
    Console.CursorLeft = 1;
    float onechunk = 30.0f / tot;

    //draw filled part
    int position = 1;
    for (int i = 0; i < onechunk * progress; i++)
    {
        Console.BackgroundColor = ConsoleColor.Green;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw unfilled part
    for (int i = position; i <= 31; i++)
    {
        Console.BackgroundColor = ConsoleColor.Gray;
        Console.CursorLeft = position++;
        Console.Write(" ");
    }

    //draw totals
    Console.CursorLeft = 35;
    Console.BackgroundColor = ConsoleColor.Black;
    Console.Write(progress.ToString() + " of " + tot.ToString() + "    "); //blanks at the end remove any excess
}

Należy pamiętać, że @ Daniel-wolf ma lepsze podejście: https://stackoverflow.com/a/31193455/169714

JP Hellemons
źródło
6

Stworzyłem tę poręczną klasę, która działa z System.Reactive. Mam nadzieję, że uznasz to za wystarczająco miłe.

public class ConsoleDisplayUpdater : IDisposable
{
    private readonly IDisposable progressUpdater;

    public ConsoleDisplayUpdater(IObservable<double> progress)
    {
        progressUpdater = progress.Subscribe(DisplayProgress);
    }

    public int Width { get; set; } = 50;

    private void DisplayProgress(double progress)
    {
        if (double.IsNaN(progress))
        {
            return;
        }

        var progressBarLenght = progress * Width;
        System.Console.CursorLeft = 0;
        System.Console.Write("[");
        var bar = new string(Enumerable.Range(1, (int) progressBarLenght).Select(_ => '=').ToArray());

        System.Console.Write(bar);

        var label = $@"{progress:P0}";
        System.Console.CursorLeft = (Width -label.Length) / 2;
        System.Console.Write(label);
        System.Console.CursorLeft = Width;
        System.Console.Write("]");
    }

    public void Dispose()
    {
        progressUpdater?.Dispose();
    }
}
SuperJMN
źródło
5

Bardzo podobał mi się pasek postępu oryginalnego plakatu, ale okazało się, że nie wyświetlał on poprawnie postępu w przypadku niektórych kombinacji postęp / łączna liczba przedmiotów. Na przykład poniższe elementy nie rysują się prawidłowo, pozostawiając dodatkowy szary blok na końcu paska postępu:

drawTextProgressBar(4114, 4114)

Ponownie wykonałem część kodu rysującego, aby usunąć niepotrzebne zapętlenie, które rozwiązało powyższy problem, a także przyspieszyło trochę:

public static void drawTextProgressBar(string stepDescription, int progress, int total)
{
    int totalChunks = 30;

    //draw empty progress bar
    Console.CursorLeft = 0;
    Console.Write("["); //start
    Console.CursorLeft = totalChunks + 1;
    Console.Write("]"); //end
    Console.CursorLeft = 1;

    double pctComplete = Convert.ToDouble(progress) / total;
    int numChunksComplete = Convert.ToInt16(totalChunks * pctComplete);

    //draw completed chunks
    Console.BackgroundColor = ConsoleColor.Green;
    Console.Write("".PadRight(numChunksComplete));

    //draw incomplete chunks
    Console.BackgroundColor = ConsoleColor.Gray;
    Console.Write("".PadRight(totalChunks - numChunksComplete));

    //draw totals
    Console.CursorLeft = totalChunks + 5;
    Console.BackgroundColor = ConsoleColor.Black;

    string output = progress.ToString() + " of " + total.ToString();
    Console.Write(output.PadRight(15) + stepDescription); //pad the output so when changing from 3 to 4 digits we avoid text shifting
}
Nico M
źródło
działa to ogólnie, z wyjątkiem tego, że usuwa poprzednie wyjścia konsoli jak każdy poprzedni tekst i że nie przechodzi do nowej linii po ...
David Shnayder
0

Właśnie natknąłem się na ten wątek, szukając czegoś innego i pomyślałem, że zostawię swój kod, który złożyłem razem, który pobiera listę plików za pomocą DownloadProgressChanged. Uważam, że jest to bardzo pomocne, więc widzę nie tylko postęp, ale także rzeczywisty rozmiar w miarę przesyłania pliku. Mam nadzieję, że to komuś pomoże!

public static bool DownloadFile(List<string> files, string host, string username, string password, string savePath)
    {
        try
        {
            //setup FTP client

            foreach (string f in files)
            {
                FILENAME = f.Split('\\').Last();
                wc.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
                wc.DownloadFileAsync(new Uri(host + f), savePath + f);
                while (wc.IsBusy)
                    System.Threading.Thread.Sleep(1000);
                Console.Write("  COMPLETED!");
                Console.WriteLine();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            return false;
        }
        return true;
    }

    private static void ProgressChanged(object obj, System.Net.DownloadProgressChangedEventArgs e)
    {
        Console.Write("\r --> Downloading " + FILENAME +": " + string.Format("{0:n0}", e.BytesReceived / 1000) + " kb");
    }

    private static void Completed(object obj, AsyncCompletedEventArgs e)
    {
    }

Oto przykład wyniku: wprowadź opis obrazu tutaj

Mam nadzieję, że to komuś pomoże!

Ted Krapf
źródło
2
@regisbsb To nie są paski postępu, wygląda na to, że ocenzurował część nazw plików :) Wiem, na początku też się oszukałem.
Silkfire
-1

Wciąż jestem trochę nowy, C#ale uważam, że poniższe informacje mogą pomóc.

string[] xmlFilePath = Directory.GetFiles(xmlFullpath, "*.xml");
Console.WriteLine("Loading XML files...");
int count = 0;
foreach (string file in xmlFilePath)
{
    //ExportXml(file, styleSheet);
    drawTextProgressBar(count, xmlCount);
    count++;
}
Jin
źródło