Advertisement
csharp wallpaper
Advertisement

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.

Advertisement
Example of Progress bar in the Console in C#
Example of Progress bar in the Console in C#

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.

Advertisement

By Enrico

My greatest passion is technology. I am interested in multiple fields and I have a lot of experience in software design and development. I started professional development when I was 6 years. Today I am a strong full-stack .NET developer (C#, Xamarin, Azure)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.