At work, I sometimes find myself needing to make Console applications in C# which take some time and I want to display a progress bar.
For example, I have a few console applications which parse dump files into objects. Another example is to insert the seed data into a database. Display a progress bar gives me the opportunity to understand the application is still running.
Usually, the progress bar representation is a simple incremental percentage display. However, I thought I’d create a generic method which would display an ASCII 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.
using System;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// An ASCII progress bar
/// </summary>
public class ProgressBar : IDisposable, IProgress<double>
{
private const int blockCount = 10;
private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8);
private const string animation = @"|/-\";
private bool showProgressBar = true;
private readonly Timer timer;
private double currentProgress = 0;
private string currentText = string.Empty;
private bool disposed = false;
private int animationIndex = 0;
public ProgressBar(bool ShowProgressBar = true)
{
showProgressBar = ShowProgressBar;
timer = new Timer(TimerHandler);
// A progress bar is only for temporary display in a console window.
// If the console output is redirected to a file, draw nothing.
// Otherwise, we'll end up with a lot of garbage in the target file.
if (!Console.IsOutputRedirected)
{
ResetTimer();
}
}
public void Report(double value)
{
// Make sure value is in [0..1] range
value = Math.Max(0, Math.Min(1, value));
Interlocked.Exchange(ref currentProgress, value);
}
private void TimerHandler(object state)
{
lock (timer)
{
if (disposed) return;
string text = "";
if (showProgressBar)
{
int progressBlockCount = (int)(currentProgress * blockCount);
int percent = (int)(currentProgress * 100);
text = string.Format("[{0}{1}] {2,3}% {3}",
new string('#', progressBlockCount),
new string('-', blockCount - progressBlockCount),
percent,
animation[animationIndex++ % animation.Length]);
}
else
{
text = animation[animationIndex++ % animation.Length].ToString();
}
UpdateText(text);
ResetTimer();
}
}
private void UpdateText(string text)
{
// Get length of common portion
int commonPrefixLength = 0;
int commonLength = Math.Min(currentText.Length, text.Length);
while (commonPrefixLength < commonLength &&
text[commonPrefixLength] == currentText[commonPrefixLength])
{
commonPrefixLength++;
}
// Backtrack to the first differing character
StringBuilder outputBuilder = new StringBuilder();
outputBuilder.Append('\b', currentText.Length - commonPrefixLength);
// Output new suffix
outputBuilder.Append(text.Substring(commonPrefixLength));
// If the new text is shorter than the old one: delete overlapping characters
int overlapCount = currentText.Length - text.Length;
if (overlapCount > 0)
{
outputBuilder.Append(' ', overlapCount);
outputBuilder.Append('\b', overlapCount);
}
Console.Write(outputBuilder);
currentText = text;
}
private void ResetTimer()
{
timer.Change(animationInterval, TimeSpan.FromMilliseconds(-1));
}
public void Dispose()
{
lock (timer)
{
disposed = true;
UpdateText(string.Empty);
}
}
}
}
Now, I can use this class for a progress bar with C# in my console application. In the following example, I only want to display the progress bar. You can replace the for
cycles with your code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.Write("Performing some task... ");
using (var progress = new ProgressBar())
{
for (int i = 0; i <= 1000; i++)
{
progress.Report((double)i / 100);
Thread.Sleep(20);
}
}
Console.WriteLine("Done.");
}
}
}
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.
If you want the code, I have created a repository on Github.