' ***********************************************************************
' Author : ElektroStudios
' Modified : 12-December-2016
' ***********************************************************************
#Region " Imports "
Imports System.Drawing
Imports System.Text
Imports System.Threading
' Imports DevCase.Core.Application.UserInterface.Tools.Console
' Imports DevCase.Core.Design
#End Region
#Region " Console ProgressBar "
'Namespace DevCase.Core.Application.UserInterface
'#If Not NET40 Then
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' A personalizable colorful ASCII progress bar for console applications.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <remarks>
''' Based on: <see href="https://gist.github.com/DanielSWolf/0ab6a96899cc5377bf54"/>
''' </remarks>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Public Module Module1
'''
''' Public Sub Main()
''' Console.CursorVisible = False
''' Console.Write("Performing some task... ")
'''
''' Using progress As New ConsoleProgressBar(blockCount:=20) With {
''' .Animation = "||//--\\".ToCharArray(),
''' .AnimationSpeed = TimeSpan.FromMilliseconds(125),
''' .AnimationBackColor = Console.BackgroundColor,
''' .AnimationForeColor = ConsoleColor.Yellow,
''' .BlockActiveChar = "█"c,
''' .BlockInactiveChar = "·"c,
''' .BlockActiveBackColor = Console.BackgroundColor,
''' .BlockActiveForeColor = ConsoleColor.Green,
''' .BlockInactiveBackColor = Console.BackgroundColor,
''' .BlockInactiveForeColor = ConsoleColor.DarkGray,
''' .BorderBackColor = Console.BackgroundColor,
''' .BorderForeColor = ConsoleColor.White,
''' .ProgressTextFormat = "{1} of {2} ({0}%)",
''' .ProgressTextBackColor = Console.BackgroundColor,
''' .ProgressTextForeColor = ConsoleColor.Gray
''' }
'''
''' For i As Integer = 0 To 100
''' progress.Report(i / 100)
''' Thread.Sleep(100)
''' Next i
''' End Using
'''
''' Console.WriteLine()
''' Console.WriteLine("Done.")
''' Console.ReadKey()
''' End Sub
'''
''' End Module
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class ConsoleProgressBar : Implements IDisposable : Implements IProgress(Of Double)
#Region " Properties "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the current progress value. From 0.0 to 1.0
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The current progress value. From 0.0 to 1.0
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public ReadOnly Property CurrentProgress As Double
Get
Return Me.currentProgressB
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' The current progress value. From 0.0 to 1.0
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private currentProgressB As Double
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the amount of blocks to display in the progress bar.
''' <para></para>
''' Default value is: 20
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The amount of blocks to display in the progress bar.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public ReadOnly Property BlockCount As Integer
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the character used to draw an active progress bar block.
''' <para></para>
''' Default value is: "#"
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The character used to draw an active progress bar block.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BlockActiveChar As Char
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the character used to draw an inactive progress bar block.
''' <para></para>
''' Default value is: "·"
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The character used to draw an inactive progress bar block.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BlockInactiveChar As Char
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the background of an active progress bar block.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the background of an active progress bar block.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BlockActiveBackColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the foreground of an active progress bar block.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the foreground of an active progress bar block.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BlockActiveForeColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the background of a inactive progress bar block.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the background of a inactive progress bar block.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BlockInactiveBackColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the foreground of a inactive progress bar block.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the foreground of a inactive progress bar block.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BlockInactiveForeColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the background of the borders of the progress bar.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the background of the borders of the progress bar.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BorderBackColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the foreground of the borders of the progress bar.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the foreground of the borders of the progress bar.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property BorderForeColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the format of the progress text, where:
''' <para></para> {0} = Percentage Value
''' <para></para> {1} = Current Value
''' <para></para> {2} = Maximum Value
''' <para></para>
''' Default value is: "{0}%"
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The format of the percentage string.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property ProgressTextFormat As String
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the background of the progress text.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the background of the borders of the progress text.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property ProgressTextBackColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the foreground of the borders of the progress text.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the foreground of the borders of the progress text.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property ProgressTextForeColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the characters used to draw the animation secuence at the very end of the progress bar.
''' <para></para>
''' Default value is: {"|"c, "|"c, "/"c, "/"c, "-"c, "-"c, "\"c, "\"c} ( that is: "||//--\\" )
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The characters used to draw the animation secuence at the very end of the progress bar.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property Animation As Char()
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the speed (framerate) of the animation secuence defined in <see cref="ConsoleProgressBar.Animation"/>.
''' <para></para>
''' Default value is: 125 milliseconds.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The speed (framerate) of the animation secuence defined in <see cref="ConsoleProgressBar.Animation"/>.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property AnimationSpeed As TimeSpan
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the background of the animation secuence.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the background of the borders of the animation secuence.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property AnimationBackColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the <see cref="ConsoleColor"/> used to paint the foreground of the borders of the animation secuence.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The <see cref="ConsoleColor"/> used to paint the foreground of the borders of the animation secuence.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property AnimationForeColor As ConsoleColor
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets a value indicating whether the animation secuence is visible in the progress bar.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' A value indicating whether the animation secuence is visible in the progress bar.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Property AnimationVisible As Boolean
#End Region
#Region " Private Fields "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' The timer.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private ReadOnly timer As Threading.Timer
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' The current animation index.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private animationIndex As Integer
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' The cursor position in the console buffer when instancing the <see cref="ConsoleProgressBar"/> class.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private cursorPos As Point
#End Region
#Region " Constructors "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Prevents a default instance of the <see cref="ConsoleProgressBar"/> class from being created.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private Sub New()
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Initializes a new instance of the <see cref="ConsoleProgressBar"/> class.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="blockCount">
''' The amount of blocks to display in the progress bar. Default value is: 20
''' </param>
''' ----------------------------------------------------------------------------------------------------
Public Sub New(Optional ByVal blockCount As Integer = 10)
If (blockCount < 2) Then
Throw New ArgumentException(message:="Block count must be a value greater than 1.", paramName:=NameOf(blockCount))
End If
Me.BlockCount = blockCount
Me.BlockActiveChar = "#"c
Me.BlockInactiveChar = "·"c
Me.BlockActiveBackColor = Console.BackgroundColor
Me.BlockActiveForeColor = Console.ForegroundColor
Me.BlockInactiveBackColor = Console.BackgroundColor
Me.BlockInactiveForeColor = Console.ForegroundColor
Me.BorderBackColor = Console.BackgroundColor
Me.BorderForeColor = Console.ForegroundColor
Me.Animation = "||//--\\".ToCharArray()
Me.AnimationSpeed = TimeSpan.FromMilliseconds(100)
Me.AnimationVisible = True
Me.AnimationBackColor = Console.BackgroundColor
Me.AnimationForeColor = Console.ForegroundColor
Me.ProgressTextFormat = "{0}%"
Me.ProgressTextBackColor = Console.BackgroundColor
Me.ProgressTextForeColor = Console.ForegroundColor
Me.timer = New Timer(AddressOf Me.TimerCallback)
Me.cursorPos = New Point(Console.CursorLeft, Console.CursorTop)
' A progress bar is only for temporary display in a console window.
' If the console output is redirected to a text file, draw nothing.
' Otherwise, we will end up with a lot of garbage in the target text file.
If Not Console.IsOutputRedirected Then
Me.ResetTimer()
End If
End Sub
#End Region
#Region " Public Methods "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Reports a progress update.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="value">
''' The value of the updated progress.
''' </param>
''' ----------------------------------------------------------------------------------------------------
Public Sub Report(ByVal value As Double) Implements IProgress(Of Double).Report
' Make sure value is in [0..1] range
value = Math.Max(0, Math.Min(1, value))
Interlocked.Exchange(Me.currentProgressB, value)
End Sub
#End Region
#Region " Private Methods "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Handles the calls from <see cref="ConsoleProgressBar.timer"/>.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="state">
''' An object containing application-specific information relevant to the
''' method invoked by this delegate, or <see langword="Nothing"/>.
''' </param>
''' ----------------------------------------------------------------------------------------------------
Private Sub TimerCallback(ByVal state As Object)
SyncLock Me.timer
If (Me.isDisposed) Then
Exit Sub
End If
Me.UpdateText()
Me.ResetTimer()
End SyncLock
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Resets the timer.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private Sub ResetTimer()
Me.timer.Change(Me.AnimationSpeed, TimeSpan.FromMilliseconds(-1))
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Updates the progress bar text.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private Sub UpdateText()
Dim currentBlockCount As Integer = CInt(Me.currentProgressB * Me.BlockCount)
Dim percent As Integer = CInt(Me.currentProgressB * 100)
Dim text As String =
String.Format("[{0}{1}] {2} {3}",
New String(Me.BlockActiveChar, currentBlockCount),
New String(Me.BlockInactiveChar, Me.BlockCount - currentBlockCount),
String.Format(Me.ProgressTextFormat, percent, (Me.currentProgressB * 100), 100),
Me.Animation(Math.Max(Interlocked.Increment(Me.animationIndex), Me.animationIndex - 1) Mod Me.Animation.Length))
Dim textLen As Integer = text.Length
Dim coloredText As New StringBuilder()
With coloredText
.AppendFormat("*B{0}**F{1}*{2}*-B**-F*", CInt(Me.BorderBackColor), CInt(Me.BorderForeColor),
"[")
.AppendFormat("*B{0}**F{1}*{2}*-B**-F*", CInt(Me.BlockActiveBackColor), CInt(Me.BlockActiveForeColor),
New String(Me.BlockActiveChar, currentBlockCount))
.AppendFormat("*B{0}**F{1}*{2}*-B**-F*", CInt(Me.BlockInactiveBackColor), CInt(Me.BlockInactiveForeColor),
New String(Me.BlockInactiveChar, Me.BlockCount - currentBlockCount))
.AppendFormat("*B{0}**F{1}*{2}*-B**-F*", CInt(Me.BorderBackColor), CInt(Me.BorderForeColor),
"]")
.Append(" ")
.AppendFormat("*B{0}**F{1}*{2}*-B**-F*", CInt(Me.ProgressTextBackColor), CInt(Me.ProgressTextForeColor),
String.Format(Me.ProgressTextFormat, percent, (Me.currentProgressB * 100), 100))
.Append(" ")
If Me.AnimationVisible Then
.AppendFormat("*B{0}**F{1}*{2}*-B**-F*", CInt(Me.AnimationBackColor), CInt(Me.AnimationForeColor),
Me.Animation(Math.Max(Interlocked.Increment(Me.animationIndex), Me.animationIndex - 1) Mod Me.Animation.Length))
End If
End With
' Lazy error handling when the console window is resized by the end-user.
Try
If (Console.BufferWidth > textLen) Then
coloredText.Append(" "c, Console.BufferWidth - text.Length)
End If
Console.SetCursorPosition(Me.cursorPos.X, Me.cursorPos.Y)
ConsoleUtil.WriteColorText(coloredText.ToString(), {"*"c})
Catch ex As Exception
' Do nothing.
End Try
End Sub
#End Region
#Region " IDisposable Implementation "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Flag to detect redundant calls when disposing.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private isDisposed As Boolean = False
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Releases all the resources used by this instance.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Sub Dispose() Implements IDisposable.Dispose
Me.Dispose(isDisposing:=True)
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
''' Releases unmanaged and, optionally, managed resources.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="isDisposing">
''' <see langword="True"/> to release both managed and unmanaged resources;
''' <see langword="False"/> to release only unmanaged resources.
''' </param>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Protected Sub Dispose(ByVal isDisposing As Boolean)
If (Not Me.isDisposed) AndAlso (isDisposing) Then
SyncLock Me.timer
Me.UpdateText()
End SyncLock
If (Me.timer IsNot Nothing) Then
Me.timer.Dispose()
End If
End If
Me.isDisposed = True
End Sub
#End Region
End Class
'#End If
'End Namespace
#End Region
'Namespace DevCase.Core.Application.UserInterface.Tools.Console
Public NotInheritable Class ConsoleUtil
#Region " Public Methods "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Writes colored text to the console.
''' <para></para>
''' Use <c>*F##*</c> as the start delimiter of the ForeColor, use <c>*-F*</c> as the end delimiter of the ForeColor.
''' <para></para>
''' Use <c>*B##*</c> as the start delimiter of the BackColor, use <c>*-B*</c> as the end delimiter of the BackColor.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' WriteColorText("*F10*Hello *F14*World!*-F*", {"*"c})
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <param name="text">
''' The color-delimited text to write.
''' </param>
'''
''' <param name="delimiters">
''' A set of 1 or 2 delimiters to parse the color-delimited string.
''' </param>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Shared Sub WriteColorText(ByVal text As String,
ByVal delimiters As Char())
' Save the current console colors to later restore them.
Dim oldForedColor As ConsoleColor = Console.ForegroundColor
Dim oldBackColor As ConsoleColor = Console.BackgroundColor
' Split the string to retrieve and parse the color-delimited strings.
Dim stringParts As String() =
text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries)
' Parse the string parts.
For Each part As String In stringParts
If (part.ToUpper Like "F#") OrElse (part.ToUpper Like "F##") Then
' Use the new ForeColor.
Console.ForegroundColor = DirectCast(CInt(part.Substring(1)), ConsoleColor)
ElseIf (part.ToUpper Like "B#") OrElse (part.ToUpper Like "B##") Then
' Use the new BackgroundColor.
Console.BackgroundColor = DirectCast(CInt(part.Substring(1)), ConsoleColor)
ElseIf (part.ToUpper Like "-F") Then
' Use the saved Forecolor.
Console.ForegroundColor = oldForedColor
ElseIf (part.ToUpper Like "-B") Then
' Use the saved BackgroundColor.
Console.BackgroundColor = oldBackColor
Else ' String part is not a delimiter so we can print it.
Console.Write(part)
End If
Next part
' Restore the saved console colors.
Console.ForegroundColor = oldForedColor
Console.BackgroundColor = oldBackColor
End Sub
#End Region
Private Sub New()
End Sub
End Class
'End Namespace