Skip to content

Instantly share code, notes, and snippets.

@lawrencekgrant
Forked from DanielSWolf/Program.cs
Last active October 3, 2018 13:12
Show Gist options
  • Select an option

  • Save lawrencekgrant/ad40e6d1dc591b33c34ad4f0a403f44f to your computer and use it in GitHub Desktop.

Select an option

Save lawrencekgrant/ad40e6d1dc591b33c34ad4f0a403f44f to your computer and use it in GitHub Desktop.
Console progress bar. Code is under the MIT License: http://opensource.org/licenses/MIT
using System;
using System.Threading;
static class Program {
static void Main() {
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.");
}
}
using System;
using System.Text;
using System.Threading;
/// <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 readonly Timer timer;
private double currentProgress = 0;
private string currentText = string.Empty;
private bool disposed = false;
private int animationIndex = 0;
public ProgressBar() {
var autoEvent = new AutoResetEvent(false);
var animationIntervalMilliseconds = (int)animationInterval.TotalMilliseconds;
timer = new Timer(TimerHandler, autoEvent, animationIntervalMilliseconds, animationIntervalMilliseconds);
// 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;
int progressBlockCount = (int) (currentProgress * blockCount);
int percent = (int) (currentProgress * 100);
string text = string.Format("[{0}{1}] {2,3}% {3}",
new string('#', progressBlockCount), new string('-', blockCount - progressBlockCount),
percent,
animation[animationIndex++ % animation.Length]);
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);
}
}
}
@lawrencekgrant
Copy link
Copy Markdown
Author

Updated to support .NET Standard with System.Threading.Timer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment