C# Console Progress Bar

At work, I sometimes find myself needing to make Console applications which take some time. For example, I have a few console applications which parse dump files into objects and then insert the data into a database. Usually, I represent the progress of these applications with a simple incremental percentage display, however I thought I'd create a generic method which would display an ASCII progress bar.

ConsoleApp_ProgressBar

At work, I sometimes find myself needing to make Console applications which take some time. For example, I have a few console applications which parse dump files into objects and then insert the data into a database. Usually, I represent the progress of these applications with a simple incremental percentage display, however I thought I'd create a generic method which would display an ASCII progress bar.

ProgressBar Class
  1. using System;
  2. using System.Text;
  3. using System.Threading;
  4.  
  5. namespace ConsoleApplication1
  6. {
  7.     /// <summary>
  8.     /// An ASCII progress bar
  9.     /// </summary>
  10.     public class ProgressBar : IDisposable, IProgress<double>
  11.     {
  12.         private const int blockCount = 10;
  13.         private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8);
  14.         private const string animation = @"|/-\";
  15.         private bool showProgressBar = true;
  16.  
  17.         private readonly Timer timer;
  18.  
  19.         private double currentProgress = 0;
  20.         private string currentText = string.Empty;
  21.         private bool disposed = false;
  22.         private int animationIndex = 0;
  23.  
  24.         public ProgressBar(bool ShowProgressBar = true)
  25.         {
  26.             showProgressBar = ShowProgressBar;
  27.             timer = new Timer(TimerHandler);
  28.  
  29.             // A progress bar is only for temporary display in a console window.
  30.             // If the console output is redirected to a file, draw nothing.
  31.             // Otherwise, we'll end up with a lot of garbage in the target file.
  32.             if (!Console.IsOutputRedirected)
  33.             {
  34.                 ResetTimer();
  35.             }
  36.         }
  37.  
  38.         public void Report(double value)
  39.         {
  40.             // Make sure value is in [0..1] range
  41.             value = Math.Max(0, Math.Min(1, value));
  42.             Interlocked.Exchange(ref currentProgress, value);
  43.         }
  44.  
  45.         private void TimerHandler(object state)
  46.         {
  47.             lock (timer)
  48.             {
  49.                 if (disposed) return;
  50.  
  51.                 string text = "";
  52.                 if (showProgressBar)
  53.                 {
  54.                     int progressBlockCount = (int)(currentProgress * blockCount);
  55.                     int percent = (int)(currentProgress * 100);
  56.                     text = string.Format("[{0}{1}] {2,3}% {3}",
  57.                            new string('#', progressBlockCount), new string('-', blockCount - progressBlockCount),
  58.                            percent,
  59.                            animation[animationIndex++ % animation.Length]);
  60.                 }
  61.                 else
  62.                 {
  63.                     text = animation[animationIndex++ % animation.Length].ToString();
  64.                 }
  65.                 UpdateText(text);
  66.  
  67.                 ResetTimer();
  68.             }
  69.         }
  70.  
  71.         private void UpdateText(string text)
  72.         {
  73.             // Get length of common portion
  74.             int commonPrefixLength = 0;
  75.             int commonLength = Math.Min(currentText.Length, text.Length);
  76.             while (commonPrefixLength < commonLength && text[commonPrefixLength] == currentText[commonPrefixLength])
  77.             {
  78.                 commonPrefixLength++;
  79.             }
  80.  
  81.             // Backtrack to the first differing character
  82.             StringBuilder outputBuilder = new StringBuilder();
  83.             outputBuilder.Append('\b', currentText.Length - commonPrefixLength);
  84.  
  85.             // Output new suffix
  86.             outputBuilder.Append(text.Substring(commonPrefixLength));
  87.  
  88.             // If the new text is shorter than the old one: delete overlapping characters
  89.             int overlapCount = currentText.Length - text.Length;
  90.             if (overlapCount > 0)
  91.             {
  92.                 outputBuilder.Append(' ', overlapCount);
  93.                 outputBuilder.Append('\b', overlapCount);
  94.             }
  95.  
  96.             Console.Write(outputBuilder);
  97.             currentText = text;
  98.         }
  99.  
  100.         private void ResetTimer()
  101.         {
  102.             timer.Change(animationInterval, TimeSpan.FromMilliseconds(-1));
  103.         }
  104.  
  105.         public void Dispose()
  106.         {
  107.             lock (timer)
  108.             {
  109.                 disposed = true;
  110.                 UpdateText(string.Empty);
  111.             }
  112.         }
  113.     }
  114. }

 

Program.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7.  
  8. namespace ConsoleApplication1
  9. {
  10.     class Program
  11.     {
  12.         static void Main(string[] args)
  13.         {
  14.             Console.Write("Performing some task... ");
  15.             using (var progress = new ProgressBar())
  16.             {
  17.                 for (int i = 0; i <= 1000; i++)
  18.                 {
  19.                     progress.Report((double)i / 100);
  20.                     Thread.Sleep(20);
  21.                 }
  22.             }
  23.             Console.WriteLine("Done.");
  24.         }
  25.     }
  26. }

The code itself is pretty self explanatory and probably more verbose than it really needs to be, but it gets the job done and looks good.

Advertsing

125X125_06

TagCloud

MonthList

CommentList