Fortschrittsbalken in der Konsolenanwendung


84

Ich schreibe eine einfache C # -Konsolen-App, die Dateien auf den SFTP-Server hochlädt. Die Anzahl der Dateien ist jedoch groß. Ich möchte entweder den Prozentsatz der hochgeladenen Dateien oder nur die Anzahl der bereits hochgeladenen Dateien aus der Gesamtzahl der hochzuladenden Dateien anzeigen.

Zuerst bekomme ich alle Dateien und die Gesamtzahl der Dateien.

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

Dann durchlaufe ich die Datei und lade sie einzeln in jeder Schleife hoch.

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

In der foreach-Schleife habe ich einen Fortschrittsbalken, mit dem ich Probleme habe. Es wird nicht richtig angezeigt.

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
}

Die Ausgabe ist nur [] 0 von 1943

Was mache ich hier falsch?

BEARBEITEN:

Ich versuche, den Fortschrittsbalken anzuzeigen, während ich XML-Dateien lade und exportiere. Es geht jedoch durch eine Schleife. Nachdem es die erste Runde beendet hat, geht es zur zweiten und so weiter.

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

Es verlässt nie die for-Schleife ... Irgendwelche Vorschläge?


Was ist xmlCount und count?
eddie_cat

Zählen Sie nur inkrementell. xmlCount ist nur die Gesamtzahl der XML-Dateien im angegebenen Ordner DirectoryInfo xmlDir = new DirectoryInfo (xmlFullpath); xmlCount = xmlDir.GetFiles (). Length;
smr5

1
Warum befindet sich die for-Schleife in einer foreach-Schleife? Es scheint über dasselbe zu iterieren. Es ist wahrscheinlich nicht notwendig, die foreach-Schleife beizubehalten.
eddie_cat

1
Haben Sie die äußere foreach-Schleife entfernt? Die Änderung des kommentierten Bits zuExportXml(xmlFilePath[i])
eddie_cat

1
Das war's. Ich habe nur die for-Schleife und es funktioniert.
smr5

Antworten:


11

Diese Zeile ist Ihr Problem:

drawTextProgressBar(0, totalCount);

Sie sagen, der Fortschritt ist in jeder Iteration Null, dies sollte erhöht werden. Verwenden Sie stattdessen möglicherweise eine for-Schleife.

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

Es hat das erste Mal funktioniert, und ich mache das Gleiche an der anderen Stelle in der gleichen Richtung und es verursacht eine Schleife. Es hört nie auf. Ich habe meinen Beitrag aktualisiert. Kannst du dir das mal anschauen? Vielen Dank.
smr5

Was hast du aktualisiert? Es sieht für mich genauso aus, was vermisse ich?
eddie_cat

200

Ich suchte auch nach einer Konsolen-Fortschrittsanzeige. Ich habe keinen gefunden, der das tat, was ich brauchte, also habe ich beschlossen, meinen eigenen zu rollen. Klicken Sie hier für den Quellcode (MIT-Lizenz).

Animierter Fortschrittsbalken

Eigenschaften:

  • Funktioniert mit umgeleiteten Ausgaben

    Wenn Sie die Ausgabe einer Konsolenanwendung umleiten (z. B. Program.exe > myfile.txt), stürzen die meisten Implementierungen mit einer Ausnahme ab. Das liegt daran , Console.CursorLeftund Console.SetCursorPosition()unterstützt nicht umgeleiteter Ausgabe.

  • Geräte IProgress<double>

    Auf diese Weise können Sie den Fortschrittsbalken mit asynchronen Vorgängen verwenden, die einen Fortschritt im Bereich von [0..1] melden.

  • Gewindesicher

  • Schnell

    Die ConsoleKlasse ist bekannt für ihre miserable Leistung. Zu viele Anrufe, und Ihre Anwendung wird langsamer. Diese Klasse führt nur 8 Aufrufe pro Sekunde aus, unabhängig davon, wie oft Sie eine Fortschrittsaktualisierung melden.

Verwenden Sie es so:

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

4
Das sieht ziemlich ordentlich aus! Würden Sie eine OSS-Lizenz wie MIT hinzufügen? choosealicense.com
Daniel Plaisted

2
Gute Idee. Habe das gemacht.
Daniel Wolf

@DanielWolf Wie bist du zu Console.Write gekommen, weil du die CursorPosition geändert hast?
JJS

1
@knocte: Im Produktionscode würde ich sicherlich. Ziel war es, das Beispiel so kurz wie möglich zu halten und nicht von den relevanten Teilen abzulenken.
Daniel Wolf

8
Das GIF ist attraktiv.
Lei Yang

18

Ich weiß, dass dies ein alter Thread ist, und entschuldige mich für die Eigenwerbung. Ich habe jedoch kürzlich eine Open-Source-Konsolenbibliothek geschrieben, die auf nuget Goblinfactory.Konsole verfügbar ist und threadsichere Unterstützung für mehrere Fortschrittsbalken bietet blockiert nicht den Haupt-Thread.

Es unterscheidet sich etwas von den obigen Antworten, da Sie die Downloads und Aufgaben parallel starten und mit anderen Aufgaben fortfahren können.

Prost, hoffe das ist hilfreich

EIN

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

gibt Ihnen diese Art der Ausgabe

Geben Sie hier die Bildbeschreibung ein

Das Nuget-Paket enthält auch Dienstprogramme zum Schreiben in einen Fensterbereich der Konsole mit vollständiger Unterstützung für das Beschneiden und Umschließen PrintAtsowie verschiedene andere hilfreiche Klassen.

Ich habe das Nuget-Paket geschrieben, weil ich ständig viele gängige Konsolenroutinen geschrieben habe, wenn ich Build- und Ops-Konsolenskripte und -Dienstprogramme geschrieben habe.

Wenn ich mehrere Dateien heruntergeladen habe, habe ich in Console.Writejedem Thread langsam zum Bildschirm gewechselt und verschiedene Tricks ausprobiert, um das Lesen der verschachtelten Ausgabe auf dem Bildschirm einfacher zu machen, z. B. verschiedene Farben oder Zahlen. Ich habe schließlich die Fensterbibliothek geschrieben, damit die Ausgabe von verschiedenen Threads einfach in verschiedene Fenster gedruckt werden kann, und sie hat eine Menge Code in meinen Dienstprogramm-Skripten reduziert.

Zum Beispiel dieser Code,

        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

produziert dies

Geben Sie hier die Bildbeschreibung ein

Sie können Fortschrittsbalken in einem Fenster genauso einfach erstellen wie in die Fenster schreiben. (mischen und Anpassen).


Dies ist einfach am besten
Pratik

9

Vielleicht möchten Sie https://www.nuget.org/packages/ShellProgressBar/ ausprobieren.

Ich bin gerade auf diese Implementierung des Fortschrittsbalkens gestoßen - die plattformübergreifende, sehr benutzerfreundliche, konfigurierbare und sofort einsatzbereite Funktion.

Nur teilen, weil es mir sehr gut gefallen hat.


6

Ich habe Ihre ProgressBarMethode kopiert . Weil Ihr Fehler als akzeptierte Antwort in der Schleife war. Die ProgressBarMethode weist jedoch auch einige Syntaxfehler auf. Hier ist die Arbeitsversion. Leicht modifiziert.

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
}

Bitte beachten Sie, dass @ Daniel-wolf einen besseren Ansatz hat: https://stackoverflow.com/a/31193455/169714


6

Ich habe diese praktische Klasse erstellt, die mit System.Reactive funktioniert. Ich hoffe du findest es schön genug.

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

5

Ich mochte den Fortschrittsbalken des Originalplakats sehr, stellte jedoch fest, dass der Fortschritt bei bestimmten Kombinationen aus Fortschritt und Gesamtelement nicht korrekt angezeigt wurde. Folgendes wird beispielsweise nicht korrekt gezeichnet, sodass am Ende des Fortschrittsbalkens ein zusätzlicher grauer Block verbleibt:

drawTextProgressBar(4114, 4114)

Ich habe einen Teil des Zeichnungscodes erneut ausgeführt, um die unnötige Schleife zu entfernen, die das oben genannte Problem behoben und die Dinge erheblich beschleunigt hat:

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
}

Dies funktioniert im Allgemeinen mit der Ausnahme, dass vorherige Konsolenausgaben wie jeder vorherige Text
gelöscht werden

0

Ich bin gerade auf diesen Thread gestoßen, als ich nach etwas anderem gesucht habe, und dachte, ich würde meinen Code, den ich zusammengestellt habe und der eine Liste von Dateien mit DownloadProgressChanged herunterlädt, ablegen. Ich finde das super hilfreich, damit ich nicht nur den Fortschritt sehe, sondern auch die tatsächliche Größe, während die Datei durchkommt. Hoffe es hilft jemandem!

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

Hier ist ein Beispiel für die Ausgabe: Geben Sie hier die Bildbeschreibung ein

Hoffe es hilft jemandem!


2
@regisbsb Das sind keine Fortschrittsbalken, es sieht so aus, als hätte er einen Teil der Dateinamen zensiert :) Ich weiß, ich habe mich zuerst auch selbst getäuscht.
Seidenfeuer

-1

Ich bin noch ein bisschen neu in, C#aber ich glaube, dass das Folgende helfen könnte.

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++;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.