DEV Community

Brandon Weaver
Brandon Weaver

Posted on

Console Application Progress Bar

I'm sure everyone has seen a progress bar in a console application at one point or another. This week, I decided to take a shot at building a progress bar system of my own.

Here's a look at the progress bar class I came up with. The design is very simple.

class ProgressBar
{
    private readonly int length;

    public ProgressBar(int length)
    {
        this.length = length;
    }

    public string GetUpdate(int progress)
    {
        progress /= 100 / this.length;

        string progressString = string.Empty;
        for (int i = 0; i < progress; i++)
        {
            progressString = $"{progressString}-";
        }

        string remainderString = string.Empty;
        for (int i = 0; i < this.length - progress; i++)
        {
            remainderString = $"{remainderString} ";
        }

        return $"{progressString}{remainderString}";
    }
}

I have a length field which controls the length (in characters) of the progress bar. In the GetUpdate method, I'm doing some simple math to get the relative progress (percentage) length. Then I simply iterate through the length of the progress and append a dash to the progress string. Finally, I iterate through the length of the progress bar minus the progress length and append a whitespace character to the remainder string before returning an interpolation of the two.

While this class could be used to represent the progress of anything we choose, I wanted to build an application which tracks the processor time of my system. The following is my implementation.

class Program
{
    static List<int> percentages;

    static ProgressBar processorTimeBar;

    static System.Diagnostics.PerformanceCounter processorCounter;

    static System.Timers.Timer timer;
    static System.Timers.Timer processorTimer;

    static void Main(string[] args)
    {
        Console.CursorVisible = false;

        processorCounter = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");

        processorTimeBar = new ProgressBar(50);

        percentages = new List<int>();

        timer = new System.Timers.Timer();
        timer.Interval = 200;

        processorTimer = new System.Timers.Timer();
        processorTimer.Interval = 50;

        timer.Elapsed += Update;
        timer.Enabled = true;

        processorTimer.Elapsed += UpdatePercentages;
        processorTimer.Enabled = true;

        Console.ReadLine();
    }

    static void Update(object sender, System.Timers.ElapsedEventArgs e)
    {
        Console.Write("Processor Time: [");
        Console.ForegroundColor = ConsoleColor.Green;
        Console.Write(processorTimeBar.GetUpdate((int)percentages.Average()));
        Console.ResetColor();
        Console.Write(']');
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($" {(int)percentages.Average():000}%");
        Console.ResetColor();
        Console.WriteLine("Press any key to exit . . . ");
        Console.SetCursorPosition(Console.CursorLeft, Console.CursorTop - 2);
        percentages = new List<int>();
    }

    static void UpdatePercentages(object sender, System.Timers.ElapsedEventArgs e)
    {
        percentages.Add((int)processorCounter.NextValue());
    }
}

There are a few noteworthy lines of code which (in all honesty) should be abstracted away in either a child of the progress bar class or the progress bar class itself.

First, you'll notice the line, Console.CursorVisible = false;. This may be a no brainer, but this line prevents the cursor from being rendered in the application. Since we will be programmatically changing the position of the cursor rapidly, this will prevent us from having a stroke. Bear in mind, however, that clicking anywhere on the terminal will enable quick edit mode, which causes cursor visibility as well as position to go off the rails. If you would like to implement a solution to this issue, you can take a look at this approach to disabling quick edit features during execution of the application.

Next, you'll notice two functions which each subscribe to the elapsed event of two timers. This isn't exactly necessary, but I wanted an average of the processor time rather than an immediate representation when the update function is called. I use a list to store processor times every fifty milliseconds and reset the list within the update function, which is called five times per second.

Finally, there's a call to Console.SetCursorPosition within the update method which allows us to write over the previous lines. This creates the illusion of a stationary progress bar.

It's important to note that without clearing the line prior to writing over it, characters which aren't written over will remain visible. My simple strategy for preventing this behavior was to format the percentage output to three spaces, although there are endless ways to tackle this issue.

It was a lot of fun working on this, and it was surprisingly much more simple than I had originally thought it would be. I'm not sure that this is even close to how you would traditionally implement such a feature, nevertheless, I'll enjoy looking for other strategies in the future.

Top comments (0)