Content Supported by Sourcelens Consulting
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "XTimer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit
'===============================================
' WARNING! DO NOT press the End button while
' debugging this project! See explanation
' at the top of the XTimerSupport module
' (XTimerS.bas).
'===============================================
' Private storage for XTimer properties:
Private mlngTimerID As Long
Private mlngInterval As Long
Private mblnEnabled As Boolean
' The XTimer's only event is Tick. XTimer's Tick event
' doesn't have any arguments (eliminating arguments speeds
' up the event slightly), but there's no reason why you
' couldn't supply arguments if you wanted to.
Event Tick()
' TimerID property is required by the EndTimer procedure,
' ---------------- in order to quickly locate the timer
' in the support module's array of active timers.
'
' There's no reason for the client to use this property,
' so it's declared Friend instead of Public.
'
Friend Property Get TimerID() As Long
TimerID = mlngTimerID
End Property
' Enabled property turns the timer on and off. This is
' ---------------- done by killing the system timer,
' because there's no way to suspend a system timer.
' If they exist, they're running.
'
Public Property Get Enabled() As Boolean
Enabled = mblnEnabled
End Property
'
Public Property Let Enabled(ByVal NewValue As Boolean)
' If there's no change to the state of
' the property, then exit. This
' prevents starting a second system
' timer when one is already running,
' etcetera.
If NewValue = mblnEnabled Then Exit Property
'
' Save the new property setting.
mblnEnabled = NewValue
'
' If the Interval is zero, the timer
' is already stopped. Don't start it.
If mlngInterval = 0 Then Exit Property
'
' Turn timer on or off.
If mblnEnabled Then
Debug.Assert mlngTimerID = 0
mlngTimerID = BeginTimer(Me, mlngInterval)
Else
' The following is necessary, because
' an XTimer can shut off its system
' timer two ways: Enabled = False,
' or Interval = 0.
If mlngTimerID <> 0 Then
Call EndTimer(Me)
mlngTimerID = 0
End If
End If
End Property
' Interval property must do more than just set the
' ----------------- timer interval. If the XTimer
' is enabled, and the Interval is changed from zero
' to a non-zero value, then a system timer must be
' started. Likewise, if the Interval is changed
' to zero, the system timer must be stopped.
'
' The Property Let procedure also ends one system timer
' and starts another whenever the interval changes.
' This is because there's no way to change the
' interval of a system timer.
'
Public Property Get Interval() As Long
Interval = mlngInterval
End Property
'
Public Property Let Interval(ByVal NewInterval As Long)
' If the new value for Interval is the same as the old,
' there's no reason to do anything.
If NewInterval = mlngInterval Then Exit Property
'
' Save the new value.
mlngInterval = NewInterval
'
' If the XTimer is active, mlngTimerID is non-zero.
' in this case, the old system timer must be
' ended before a new one is started.
If mlngTimerID <> 0 Then
Call EndTimer(Me)
mlngTimerID = 0
End If
'
' If the new interval is zero, then the XTimer
' becomes inactive, regardless of the current
' value of Enabled. If the new interval is
' not zero, AND the Enabled property is True,
' then a new system timer is started, and its
' ID is stored in mlngTimerID.
If (NewInterval <> 0) And mblnEnabled Then
mlngTimerID = BeginTimer(Me, NewInterval)
End If
End Property
' RaiseTick method is called by the support module when
' ---------------- the system timer event occurs for
' this XTimer object's system timer.
'
' Implementation detail: You might expect to declare
' this method Friend instead of Public, as there's
' no need for the client to call RaiseTick. However,
' it's critical that RaiseTick be declared Public,
' because the XTimer might be released while the
' Tick event is still being handled. An object will
' not terminate while one of its Public methods is
' on the stack, but it CAN terminate while one of its
' Friend methods is on the stack. If the object
' terminates before the Friend method returns (which
' could happen if the client executes a lot of code
' in the XTimer's Tick event), a GPF will result.
' (Note that this is a highly unusual scenario that
' depends on an external event; it does not occur in
' ordinary use of Friend functions.)
'
Public Sub RaiseTick()
RaiseEvent Tick
End Sub
Private Sub Class_Terminate()
' When the client releases its last reference to
' an XTimer object, it goes away -- but only
' if the XTimer's Enabled property is False,
' or its Interval property is True!
'
' This is because while the XTimer's system
' timer is running, the XTimerSupport module
' has to have a reference to the XTimer in
' order to raise its Tick event. Thus,
' failure of the client to disable XTimer
' objects before releasing them will LEAK
' system timers!
'
' These leaked system timers will not be
' recovered until the XTimers component shuts
' down -- that is, when the client using
' the DLL shuts down. The DLL will NOT
' unload when all XTimer objects are released,
' because references to public objects (in
' this case, those held by XTimerSupport)
' will prevent a DLL from unloading.
'
' So why bother to clean up the system timer
' in the Terminate event? Because when the
' DLL is getting shut down, all references
' to the XTimer object will be cleaned up
' -- and the XTimer will get its Terminate
' event. The system timer should be
' destroyed at this point.
On Error Resume Next
If mlngTimerID <> 0 Then KillTimer 0, mlngTimerID
'
' The following is what XTimer should do if
' it could somehow be released prior to
' DLL shutdown.
'If mlngTimerID <> 0 Then Call EndTimer(Me)
End Sub