' ***********************************************************************
' Author : Elektro
' Last Modified On : 26-September-2015
' ***********************************************************************
' <copyright file="TimeMeasurer.vb" company="Elektro Studios">
' Copyright (c) Elektro Studios. All rights reserved.
' </copyright>
' ***********************************************************************
#Region " Usage Examples "
#End Region
#Region " Option Statements "
Option Strict On
Option Explicit On
Option Infer Off
#End Region
#Region " Imports "
Imports System
Imports System.Diagnostics
Imports System.Linq
Imports System.Windows.Forms
#End Region
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Measures the elapsed and/or remaining time of a time interval.
''' The time measurer can be used as a chronometer or a countdown.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class TimeMeasurer : Implements IDisposable
#Region " Objects "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' An <see cref="Stopwatch"/> instance to retrieve the elapsed time. (chronometer)
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private timeElapsed As Stopwatch
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' A <see cref="TimeSpan"/> instance to retrieve the remaining time. (countdown)
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private timeRemaining As TimeSpan
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' A <see cref="Timer"/> instance that updates the elapsed and remaining time, and also raise <see cref="TimeMeasurer"/> events.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private WithEvents measureTimer As Timer
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Flag that indicates wheter this <see cref="TimeMeasurer"/> instance has finished to measure time interval.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private isFinished As Boolean
#End Region
#Region " Properties "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the maximum time that the <see cref="TimeMeasurer"/> can measure, in milliseconds.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The maximum time that the <see cref="TimeMeasurer"/> can measure, in milliseconds.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public Shared ReadOnly Property MaxValue As Double
Get
Return (TimeSpan.MaxValue.TotalMilliseconds - 1001.0R)
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the current state of this <see cref="TimeMeasurer"/> instance.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The update interval.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public ReadOnly Property State As TimeMeasurerState
Get
If (Me.timeElapsed Is Nothing) OrElse (Me.isFinished) Then
Return TimeMeasurerState.Disabled
ElseIf Not Me.timeElapsed.IsRunning Then
Return TimeMeasurerState.Paused
Else
Return TimeMeasurerState.Enabled
End If
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets or sets the update interval.
''' Maximum value is 1000 (1 second).
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The update interval.
''' </value>
''' ----------------------------------------------------------------------------------------------------
''' <exception cref="ArgumentException">
''' A value smaller than 1000 is required.;value
''' </exception>
Public Property UpdateInterval As Integer
Get
Return Me.updateIntervalB
End Get
<DebuggerHidden>
<DebuggerStepThrough>
Set(ByVal value As Integer)
If (value > 1000) Then
Throw New ArgumentException(message:="A value smaller than 1000 is required.", paramName:="value")
Else
Me.updateIntervalB = value
If (Me.measureTimer IsNot Nothing) Then
Me.measureTimer.Interval = value
End If
End If
End Set
End Property
''' <summary>
''' The update interval.
''' </summary>
Private updateIntervalB As Integer = 100I
#End Region
#Region " Enumerations "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Specifies the current state of a <see cref="TimeMeasurer"/> instance.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Public Enum TimeMeasurerState As Integer
''' <summary>
''' The <see cref="TimeMeasurer"/> is running.
''' </summary>
Enabled = &H0I
''' <summary>
''' The <see cref="TimeMeasurer"/> is paused.
''' </summary>
Paused = &H1I
''' <summary>
''' The <see cref="TimeMeasurer"/> is fully stopped, it cannot be resumed.
''' </summary>
Disabled = &H2I
End Enum
#End Region
#Region " Events "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Occurs when the elapsed/remaining time updates.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Public Event TimeUpdated(ByVal sender As Object, ByVal e As TimeUpdatedEventArgs)
#Region " Time Updated EventArgs "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Defines the <see cref="TimeUpdated"/> event arguments.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class TimeUpdatedEventArgs : Inherits EventArgs
#Region " Properties "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the elapsed time.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The elapsed time.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public ReadOnly Property Elapsed As TimeSpan
Get
Return Me.elapsedB
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' ( Baking Field )
''' The elapsed time.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private ReadOnly elapsedB As TimeSpan
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the remaining time.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The remaining time.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public ReadOnly Property Remaining As TimeSpan
Get
Return Me.remainingB
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' ( Baking Field )
''' The remaining time.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private ReadOnly remainingB As TimeSpan
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets the goal time.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <value>
''' The goal time.
''' </value>
''' ----------------------------------------------------------------------------------------------------
Public ReadOnly Property Goal As TimeSpan
Get
Return Me.goalB
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' ( Baking Field )
''' The goal time.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private ReadOnly goalB As TimeSpan
#End Region
#Region " Constructors "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Prevents a default instance of the <see cref="TimeUpdatedEventArgs"/> class from being created.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private Sub New()
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Initializes a new instance of the <see cref="TimeUpdatedEventArgs"/> class.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="elapsed">
''' The elapsed time.
''' </param>
'''
''' <param name="remaining">
''' The remaining time.
''' </param>
'''
''' <param name="goal">
''' The goal time.
''' </param>
''' ----------------------------------------------------------------------------------------------------
Public Sub New(ByVal elapsed As TimeSpan,
ByVal remaining As TimeSpan,
ByVal goal As TimeSpan)
Me.elapsedB = elapsed
Me.remainingB = remaining
Me.goalB = goal
End Sub
#End Region
End Class
#End Region
#End Region
#Region " Public Methods "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Starts the time interval measurement.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="milliseconds">
''' The time interval to measure, in milliseconds.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <exception cref="ArgumentOutOfRangeException">
''' milliseconds;A value smaller than <see cref="TimeMeasurer.MaxValue"/> is required.
''' </exception>
'''
''' <exception cref="ArgumentOutOfRangeException">
''' milliseconds;A value greater than 0 is required.
''' </exception>
''' ----------------------------------------------------------------------------------------------------
<DebuggerHidden>
<DebuggerStepThrough>
Public Sub Start(ByVal milliseconds As Double)
If (milliseconds > TimeMeasurer.MaxValue) Then
Throw New ArgumentOutOfRangeException(paramName:="milliseconds",
message:=String.Format("A value smaller than {0} is required.", TimeMeasurer.MaxValue))
ElseIf (milliseconds <= 0) Then
Throw New ArgumentOutOfRangeException(paramName:="milliseconds",
message:="A value greater than 0 is required.")
Else
Me.timeElapsed = New Stopwatch
Me.timeRemaining = TimeSpan.FromMilliseconds(milliseconds)
Me.measureTimer = New Timer With
{
.Tag = milliseconds,
.Interval = Me.updateIntervalB,
.Enabled = True
}
Me.isFinished = False
Me.timeElapsed.Start()
Me.measureTimer.Start()
End If
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Starts a time interval measurement given a difference between two dates.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="startDate">
''' The starting date.
''' </param>
'''
''' <param name="endDate">
''' The ending date.
''' </param>
''' ----------------------------------------------------------------------------------------------------
<DebuggerHidden>
<DebuggerStepThrough>
Public Sub Start(ByVal startDate As Date,
ByVal endDate As Date)
Me.Start(endDate.Subtract(startDate).TotalMilliseconds)
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Starts a time interval measurement given a <see cref="TimeSpan"/>.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="time">
''' A <see cref="TimeSpan"/> instance that contains the time interval.
''' </param>
''' ----------------------------------------------------------------------------------------------------
<DebuggerHidden>
<DebuggerStepThrough>
Public Sub Start(ByVal time As TimeSpan)
Me.Start(time.TotalMilliseconds)
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Pauses the time interval measurement.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <exception cref="Exception">
''' TimeMeasurer is not running.
''' </exception>
''' ----------------------------------------------------------------------------------------------------
<DebuggerHidden>
<DebuggerStepThrough>
Public Sub Pause()
If (Me.State <> TimeMeasurerState.Enabled) Then
Throw New Exception("TimeMeasurer is not running.")
Else
Me.measureTimer.Stop()
Me.timeElapsed.Stop()
End If
End Sub
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Resumes the time interval measurement.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <exception cref="Exception">
''' TimeMeasurer is not paused.
''' </exception>
''' ----------------------------------------------------------------------------------------------------
<DebuggerHidden>
<DebuggerStepThrough>
Public Sub [Resume]()
If (Me.State <> TimeMeasurerState.Paused) Then
Throw New Exception("TimeMeasurer is not paused.")
Else
Me.measureTimer.Start()
Me.timeElapsed.Start()
End If
End Sub
''' <summary>
''' Stops the time interval measurement.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <exception cref="Exception">
''' TimeMeasurer is not running.
''' </exception>
''' ----------------------------------------------------------------------------------------------------
<DebuggerHidden>
<DebuggerStepThrough>
Public Sub [Stop]()
If (Me.State = TimeMeasurerState.Disabled) Then
Throw New Exception("TimeMeasurer is not running.")
Else
Me.Reset()
Me.isFinished = True
Me.measureTimer.Stop()
Me.timeElapsed.Stop()
End If
End Sub
#End Region
#Region " Private Methods "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Stops Time intervals and resets the elapsed and remaining time to zero.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private Sub Reset()
Me.measureTimer.Stop()
Me.timeElapsed.Reset()
End Sub
#End Region
#Region " Event Handlers "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Handles the <see cref="Timer.Tick"/> event of the <see cref="MeasureTimer"/> timer.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="sender">
''' The source of the event.
''' </param>
'''
''' <param name="e">
''' The <see cref="EventArgs"/> instance containing the event data.
''' </param>
''' ----------------------------------------------------------------------------------------------------
Private Sub MeasureTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) _
Handles measureTimer.Tick
Dim timeDiff As TimeSpan = (Me.timeRemaining - Me.timeElapsed.Elapsed)
' If finished...
If (timeDiff.TotalMilliseconds <= 0.0R) _
OrElse (Me.timeElapsed.ElapsedMilliseconds > DirectCast(Me.measureTimer.Tag, Double)) Then
Me.Reset()
Me.isFinished = True
If (Me.TimeUpdatedEvent IsNot Nothing) Then
Dim goal As TimeSpan = TimeSpan.FromMilliseconds(DirectCast(Me.measureTimer.Tag, Double))
RaiseEvent TimeUpdated(sender, New TimeUpdatedEventArgs(goal, TimeSpan.FromMilliseconds(0.0R), goal))
End If
Else ' If not finished...
If (Me.TimeUpdatedEvent IsNot Nothing) Then
RaiseEvent TimeUpdated(sender, New TimeUpdatedEventArgs(Me.timeElapsed.Elapsed,
timeDiff,
TimeSpan.FromMilliseconds(DirectCast(Me.measureTimer.Tag, Double))))
End If
End If
End Sub
#End Region
#Region " IDisposable Support "
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' 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)
GC.SuppressFinalize(obj:=Me)
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>
Private Sub Dispose(ByVal isDisposing As Boolean)
If (Not Me.isDisposed) AndAlso (isDisposing) Then
If (Me.measureTimer IsNot Nothing) Then
Me.measureTimer.Stop()
Me.measureTimer.Dispose()
End If
If (Me.TimeUpdatedEvent IsNot Nothing) Then
RemoveHandler Me.TimeUpdated, Me.TimeUpdatedEvent
End If
End If
Me.isDisposed = True
End Sub
#End Region
End Class