104

I'm writing a simple c# console app that uploads files to sftp server. However, the amount of files are large. I would like to display either percentage of files uploaded or just the number of files upload already from the total number of files to be upload.

First, I get all the files and the total number of files.

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

Then I loop through the file and upload them one by one in foreach loop.

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 the foreach loop I have a progress bar which I have issues with. It doesn't display properly.

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
}

The output is just [ ] 0 out of 1943

What am I doing wrong here?

EDIT:

I'm trying to display the progress bar while I'm loading and exporting XML files. However, it's going through a loop. After it finishes the first round it goes to the second and so on.

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

It never leaves the for loop...Any suggestions?

cramopy
  • 3,459
  • 6
  • 28
  • 42
smr5
  • 2,593
  • 6
  • 39
  • 66
  • What's xmlCount and count? – eddie_cat Jul 23 '14 at 19:50
  • Count just increment. xmlCount is just the total number of XML files in the specified folder DirectoryInfo xmlDir = new DirectoryInfo(xmlFullpath); xmlCount = xmlDir.GetFiles().Length; – smr5 Jul 23 '14 at 19:54
  • I think you should try `drawTextProgressBar(i, xmlFilePath.Length)`. I'm still not sure what the purpose of that `count` is, though. Also, when you step through the code what happens? Where exactly is it getting stuck? – eddie_cat Jul 23 '14 at 19:57
  • 1
    Also, why is the for loop inside a foreach loop? It seems to be iterating over the same thing. It's probably not necessary to keep the foreach loop. – eddie_cat Jul 23 '14 at 20:00
  • Having xmlFilePath.Length is giving me the same output. The code doesn't choke. Currently there're 1493 xml files in the folder. Once it reached the 1493 i starts at 0 again. – smr5 Jul 23 '14 at 20:23
  • 1
    Did you remove the outer foreach loop? The change the commented bit to `ExportXml(xmlFilePath[i])` – eddie_cat Jul 23 '14 at 20:25
  • 1
    That was it. I just have the for loop and it works. – smr5 Jul 23 '14 at 20:26

11 Answers11

259

I was also looking for a console progress bar. I didn't find one that did what I needed, so I decided to roll my own. Click here for the source code (MIT License).

Animated progress bar

Features:

  • Works with redirected output

    If you redirect the output of a console application (e.g., Program.exe > myfile.txt), most implementations will crash with an exception. That's because Console.CursorLeft and Console.SetCursorPosition() don't support redirected output.

  • Implements IProgress<double>

    This allows you to use the progress bar with async operations that report a progress in the range of [0..1].

  • Thread-safe

  • Fast

    The Console class is notorious for its abysmal performance. Too many calls to it, and your application slows down. This class performs only 8 calls per second, no matter how often you report a progress update.

Use it like this:

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
  • 12,855
  • 13
  • 54
  • 80
  • 4
    This looks pretty neat! Would you consider adding an OSS license such as MIT to it? http://choosealicense.com/ – Daniel Plaisted Jul 23 '15 at 21:19
  • 2
    Good idea. Done that. – Daniel Wolf Jul 25 '15 at 18:20
  • @DanielWolf how did you get Console.Write from changing the CursorPosition? – JJS Oct 12 '15 at 18:51
  • @JJS If you look at the UpdateText() method in the linked code, you'll see it outputs backspace characters (\b). On a console, this moves the cursor one position to the left. – Daniel Wolf Oct 13 '15 at 07:10
  • @DanielWolf. Thanks! I certainly learned something today about the Console buffer I didn't think about before. – JJS Oct 13 '15 at 15:20
  • @DanielWolf: you should maybe replace `100` in your code sample to `COUNT`, to make it more clear – knocte Feb 04 '16 at 04:36
  • 1
    @knocte: In production code, I certainly would. The goal here was to keep the example as concise as possible and not to distract from the relevant parts. – Daniel Wolf Feb 05 '16 at 07:17
  • 11
    The gif is attractive. – Lei Yang Aug 09 '16 at 03:01
  • @DanielWolf how would someone use this if they were to copy a 10gb file for example? – user1234433222 Nov 05 '16 at 10:46
  • @Werdna That depends on the API you use for copying. It should give you some kind of progress callback. Use that to call `progress.Report` with the progress (between 0.0 and 1.0). – Daniel Wolf Nov 08 '16 at 08:37
  • @DanielWolf Not sure if I'm using incorrectly - how do you use this with a For Loop process? I've got it to kind of work but it never shows 100% even though it finishes with DONE. I tried setting `progress.Report(1)` manually to complete the task but it doesn't always work it seems. Here is what I tried: https://pastebin.com/RmimwCR4 – Justin Apr 23 '18 at 11:58
  • @Justin This behavior is by design. When the ProgressBar is disposed, it removes its output from the console. Depending on timing, "100%" may appear for a fraction of a second before being cleared, or it may not. (Remember that the implementation uses a timer.) Sure, the code could make sure that the last value is actually written before being cleared. But the difference would hardly be noticeable. – Daniel Wolf Apr 23 '18 at 12:12
  • @DanielWolf hey, as the creator of the progress bar example which I've been trying to implement with my file copying software, do you think you could answer this? https://stackoverflow.com/questions/56207489/how-to-sync-progress-bar-with-process-console – zadders May 19 '19 at 12:32
30

I know this is an old thread, and apologies for the self promotion, however I've recently written an open source console library available on nuget Goblinfactory.Konsole with threadsafe multiple progress bar support, that might help anyone new to this page needing one that doesnt block the main thread.

It's somewhat different to the answers above as it allows you to kick off the downloads and tasks in parallel and continue with other tasks;

cheers, hope this is helpful

A

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

gives you this type of output

enter image description here

The nuget package also includes utilities for writing to a windowed section of the console with full clipping and wrapping support, plus PrintAt and various other helpful classes.

I wrote the nuget package because I constantly ended up writing lots of common console routines whenever I wrote build and ops console scripts and utilities.

If I was downloading several files, I used to slowly Console.Write to the screen on each thread, and used to try various tricks to make reading the interleaved output on the screen easier to read, e.g. different colors or numbers. I eventually wrote the windowing library so that output from different threads could simply be printed to different windows, and it cut down a ton of boilerplate code in my utility scripts.

For example, this 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

produces this

enter image description here

You can also create progress bars inside a window just as easily as writing to the windows. (mix and match).

snowcode
  • 1,033
  • 10
  • 24
17

You might want to try https://www.nuget.org/packages/ShellProgressBar/

I just stumbled upon this progress bar implementation - its cross platform, really easy to use, quite configurable and does what it should right out of the box.

Just sharing because i liked it a lot.

Schweder
  • 1,464
  • 2
  • 13
  • 19
13

This line is your problem:

drawTextProgressBar(0, totalCount);

You're saying the progress is zero in every iteration, this should be incremented. Maybe use a for loop instead.

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);
}
cramopy
  • 3,459
  • 6
  • 28
  • 42
eddie_cat
  • 2,527
  • 4
  • 25
  • 43
  • It worked the first time, and I'm doing the same thing in the other place in the same up and it's causing a loop. It never stops. I updated my post. Can you take a look at it? Thanks. – smr5 Jul 23 '14 at 19:45
  • What did you update? It looks the same to me, what am I missing? – eddie_cat Jul 23 '14 at 19:47
10

I quite liked the original poster's progress bar, but found that it did not display progress correctly with certain progress/total item combinations. The following, for example, does not draw correctly, leaving an extra grey block at the end of the progress bar:

drawTextProgressBar(4114, 4114)

I re-did some of the drawing code to remove the unnecessary looping which fixed the above issue and also sped things up quite a bit:

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
  • 173
  • 1
  • 8
  • this works in general except that it deletes previous console outputs like any text before that and that it doesn't go to a new line after.... – David Shnayder Jan 15 '20 at 12:13
  • 1
    I loved this implementation. Something fun to do is use conditional text to make this a lot of fun. So if progress % 10 is 3 output a “-“, if mod 6, “|”. I also used this progress bar to sing a song by using sub string $”{lyrics}{lyrics}”.substring(progress % lyrics.length, 20). It’s easy, you don’t need a package or api and since it’s that simple you can have fun with it. Great contribution. – John Harrison Jul 16 '22 at 15:33
7

Console Progress Bar in C# (Complete Code - Just copy and paste on your IDE)

class ConsoleUtility
{
    const char _block = '■';
    const string _back = "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
    const string _twirl = "-\\|/";

    public static void WriteProgressBar(int percent, bool update = false)
    {
        if (update)
            Console.Write(_back);
        Console.Write("[");
        var p = (int)((percent / 10f) + .5f);
        for (var i = 0; i < 10; ++i)
        {
            if (i >= p)
                Console.Write(' ');
            else
                Console.Write(_block);
        }
        Console.Write("] {0,3:##0}%", percent);
    }

    public static void WriteProgress(int progress, bool update = false)
    {
        if (update)
            Console.Write("\b");
        Console.Write(_twirl[progress % _twirl.Length]);
    }
}


//This is how to call in your main method
static void Main(string[] args)
{
    ConsoleUtility.WriteProgressBar(0);
    for (var i = 0; i <= 100; ++i)
    {
       ConsoleUtility.WriteProgressBar(i, true);
       Thread.Sleep(50);
    }

    Console.WriteLine();
    ConsoleUtility.WriteProgress(0);
    for (var i = 0; i <= 100; ++i)
    {
        ConsoleUtility.WriteProgress(i, true);
        Thread.Sleep(50);
    }
}

Output: C# Progress Bar

Arshman Saleem
  • 135
  • 1
  • 7
6

I have copy pasted your ProgressBar method. Because your error was in the loop as the accepted answer mentioned. But the ProgressBar method has some syntax errors too. Here is the working version. Slightly modified.

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
}

Please note that @Daniel-wolf has a better approach: https://stackoverflow.com/a/31193455/169714

Community
  • 1
  • 1
JP Hellemons
  • 5,977
  • 11
  • 63
  • 128
5

I've created this handy class that works with System.Reactive. I hope you find it lovely enough.

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
  • 13,110
  • 16
  • 86
  • 185
0

I just stumbled upon this thread looking for something else, and I thought I'd drop off my code that I put together that downloads a List of files using the DownloadProgressChanged. I find this super helpful so I not only see the progress, but the actual size as the file is coming through. Hope it helps someone!

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

Here's an example of the output: enter image description here

Hope it helps someone!

Ted Krapf
  • 403
  • 3
  • 11
  • 2
    @regisbsb Those are not progress bars, it looks like he censored part of the file names :) I know, I was fooled myself too at first. – silkfire Jun 23 '16 at 06:19
0

I am still a little new to C# but I believe the below might help.

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++;
}
Rashwan L
  • 38,237
  • 7
  • 103
  • 107
Jin
  • 21
0

Based on all posts above, I did an improved version.

  1. No jumping cursors. It's invisible now.

  2. Improved performance (costs 1/5~1/10 time of the origin one).

  3. Interface based. Easy to move to something else.

     public class ConsoleProgressBar : IProgressBar
     {
         private const ConsoleColor ForeColor = ConsoleColor.Green;
         private const ConsoleColor BkColor = ConsoleColor.Gray;
         private const int DefaultWidthOfBar = 32;
         private const int TextMarginLeft = 3;
    
         private readonly int _total;
         private readonly int _widthOfBar;
    
         public ConsoleProgressBar(int total, int widthOfBar = DefaultWidthOfBar)
         {
             _total = total;
             _widthOfBar = widthOfBar;
         }
    
         private bool _intited;
         public void Init()
         {
             _lastPosition = 0;
    
             //Draw empty progress bar
             Console.CursorVisible = false;
             Console.CursorLeft = 0;
             Console.Write("["); //start
             Console.CursorLeft = _widthOfBar;
             Console.Write("]"); //end
             Console.CursorLeft = 1;
    
             //Draw background bar
             for (var position = 1; position < _widthOfBar; position++) //Skip the first position which is "[".
             {
                 Console.BackgroundColor = BkColor;
                 Console.CursorLeft = position;
                 Console.Write(" ");
             }
         }
    
         public void ShowProgress(int currentCount)
         {
             if (!_intited)
             {
                 Init();
                 _intited = true;
             }
             DrawTextProgressBar(currentCount);
         }
    
         private int _lastPosition;
    
         public void DrawTextProgressBar(int currentCount)
         {
             //Draw current chunk.
             var position = currentCount * _widthOfBar / _total;
             if (position != _lastPosition)
             {
                 _lastPosition = position;
                 Console.BackgroundColor = ForeColor;
                 Console.CursorLeft = position >= _widthOfBar ? _widthOfBar - 1 : position;
                 Console.Write(" ");
             }
    
             //Draw totals
             Console.CursorLeft = _widthOfBar + TextMarginLeft;
             Console.BackgroundColor = ConsoleColor.Black;
             Console.Write(currentCount + " of " + _total + "    "); //blanks at the end remove any excess
         }
     }
    
     public interface IProgressBar
     {
         public void ShowProgress(int currentCount);
     }
    

And some test code:

        var total = 100;
        IProgressBar progressBar = new ConsoleProgressBar(total);
        for (var i = 0; i <= total; i++)
        {
            progressBar.ShowProgress(i);
            Thread.Sleep(50);
        }

        Thread.Sleep(500);
        Console.Clear();

        total = 9999;
        progressBar = new ConsoleProgressBar(total);
        for (var i = 0; i <= total; i++)
        {
            progressBar.ShowProgress(i);
        }
cheny
  • 2,545
  • 1
  • 24
  • 30