Barra de progreso en la aplicación de consola


83

Estoy escribiendo una aplicación de consola c # simple que carga archivos al servidor sftp. Sin embargo, la cantidad de archivos es grande. Me gustaría mostrar el porcentaje de archivos cargados o solo el número de archivos cargados ya del número total de archivos que se cargarán.

Primero, obtengo todos los archivos y el número total de archivos.

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

Luego, recorro el archivo y los subo uno por uno en 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);
}

En el bucle foreach tengo una barra de progreso con la que tengo problemas. No se muestra correctamente.

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
}

La salida es solo [] 0 de 1943

¿Qué estoy haciendo mal aquí?

EDITAR:

Estoy intentando mostrar la barra de progreso mientras estoy cargando y exportando archivos XML. Sin embargo, está pasando por un bucle. Después de que termina la primera ronda, pasa a la segunda y así sucesivamente.

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

Nunca sale del bucle for ... ¿Alguna sugerencia?


¿Qué es xmlCount y count?
eddie_cat

Cuente solo incremente. xmlCount es solo el número total de archivos XML en la carpeta especificada DirectoryInfo xmlDir = new DirectoryInfo (xmlFullpath); xmlCount = xmlDir.GetFiles (). Longitud;
smr5

1
Además, ¿por qué el bucle for está dentro de un bucle foreach? Parece estar iterando sobre lo mismo. Probablemente no sea necesario mantener el bucle foreach.
eddie_cat

1
¿Quitaste el bucle exterior de cada bucle? El cambio del bit comentado aExportXml(xmlFilePath[i])
eddie_cat

1
Eso fue todo. Solo tengo el bucle for y funciona.
smr5

Respuestas:


11

Esta línea es tu problema:

drawTextProgressBar(0, totalCount);

Estás diciendo que el progreso es cero en cada iteración, esto debería incrementarse. Quizás use un bucle for en su lugar.

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

Funcionó la primera vez, y estoy haciendo lo mismo en el otro lugar de la misma manera y está causando un bucle. Nunca se detiene. Actualicé mi publicación. ¿Puedes mirar esto? Gracias.
smr5

¿Qué actualizaste? A mí me parece lo mismo, ¿qué me estoy perdiendo?
eddie_cat

196

También estaba buscando una barra de progreso de la consola. No encontré uno que hiciera lo que necesitaba, así que decidí rodar el mío. Haga clic aquí para obtener el código fuente (licencia MIT).

Barra de progreso animada

caracteristicas:

  • Funciona con salida redirigida

    Si redirige la salida de una aplicación de consola (por ejemplo, Program.exe > myfile.txt), la mayoría de las implementaciones fallarán con una excepción. Eso es porque Console.CursorLefty Console.SetCursorPosition()no es compatible con la salida redirigida.

  • Implementos IProgress<double>

    Esto le permite usar la barra de progreso con operaciones asincrónicas que informan un progreso en el rango de [0..1].

  • A salvo de amenazas

  • Rápido

    La Consoleclase es conocida por su pésimo desempeño. Demasiadas llamadas y su aplicación se ralentiza. Esta clase realiza solo 8 llamadas por segundo, sin importar la frecuencia con la que informe una actualización de progreso.

Úselo así:

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

3
¡Esto se ve bastante bien! ¿Consideraría agregarle una licencia OSS como MIT? choosealicense.com
Daniel Plaisted

2
Buena idea. Hecho eso.
Daniel Wolf

@DanielWolf ¿cómo obtuviste Console.Write al cambiar CursorPosition?
JJS

1
@knocte: En código de producción, ciertamente lo haría. El objetivo aquí era mantener el ejemplo lo más conciso posible y no distraer la atención de las partes relevantes.
Daniel Wolf

8
El gif es atractivo.
Lei Yang

16

Sé que este es un hilo antiguo y me disculpo por la autopromoción, sin embargo, recientemente escribí una biblioteca de consola de código abierto disponible en nuget Goblinfactory.Konsole con soporte de barra de progreso múltiple seguro para subprocesos, que podría ayudar a cualquier persona nueva en esta página que necesite una que no bloquea el hilo principal.

Es algo diferente a las respuestas anteriores, ya que le permite iniciar las descargas y tareas en paralelo y continuar con otras tareas;

salud, espero que esto sea útil

UNA

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

te da este tipo de salida

ingrese la descripción de la imagen aquí

El paquete nuget también incluye utilidades para escribir en una sección con ventana de la consola con soporte completo de recorte y ajuste, además de PrintAtvarias otras clases útiles.

Escribí el paquete nuget porque constantemente terminaba escribiendo muchas rutinas de consola comunes cada vez que escribía scripts y utilidades de consola de construcción y operaciones.

Si estaba descargando varios archivos, solía ir lentamente Console.Writea la pantalla en cada hilo y solía probar varios trucos para que la lectura de la salida intercalada en la pantalla sea más fácil de leer, por ejemplo, diferentes colores o números. Eventualmente escribí la biblioteca de ventanas para que la salida de diferentes subprocesos pudiera simplemente imprimirse en diferentes ventanas, y redujo una tonelada de código repetitivo en mis scripts de utilidad.

Por ejemplo, este código,

        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

produce esto

ingrese la descripción de la imagen aquí

También puede crear barras de progreso dentro de una ventana tan fácilmente como escribir en las ventanas. (mezclar y combinar).


Esto es lo mejor
Pratik


6

He copiado y pegado tu ProgressBarmétodo. Porque su error estaba en el ciclo como se menciona en la respuesta aceptada. Pero el ProgressBarmétodo también tiene algunos errores de sintaxis. Aquí está la versión de trabajo. Ligeramente modificado.

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
}

Tenga en cuenta que @ Daniel-wolf tiene un mejor enfoque: https://stackoverflow.com/a/31193455/169714


5

Me gustó bastante la barra de progreso del póster original, pero descubrí que no mostraba el progreso correctamente con ciertas combinaciones de progreso / elementos totales. Lo siguiente, por ejemplo, no se dibuja correctamente, dejando un bloque gris adicional al final de la barra de progreso:

drawTextProgressBar(4114, 4114)

Rehice parte del código de dibujo para eliminar el bucle innecesario que solucionó el problema anterior y también aceleró bastante las cosas:

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
}

esta obra en general, excepto que elimina salidas de la consola anteriores como cualquier texto antes de que y que no va a una nueva línea después ....
David shnayder

5

He creado esta práctica clase que funciona con System.Reactive. Espero que lo encuentres lo suficientemente encantador.

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

0

Me encontré con este hilo buscando algo más, y pensé en dejar mi código que armé y que descarga una Lista de archivos usando DownloadProgressChanged. Encuentro esto muy útil, por lo que no solo veo el progreso, sino también el tamaño real a medida que avanza el archivo. ¡Espero que ayude a alguien!

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

Aquí tienes un ejemplo de la salida: ingrese la descripción de la imagen aquí

¡Espero que ayude a alguien!


2
@regisbsb Esas no son barras de progreso, parece que censuró parte de los nombres de los archivos :) Lo sé, yo también me engañé al principio.
Silkfire

-1

Todavía soy un poco nuevo, C#pero creo que lo siguiente podría ayudar.

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++;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.