Useful Components

The following components were developed for use within TradeBuild, but are also useful in other contexts and are made available for download here.

The currently available components are:

To install these components, click here (this is version 2.0.0.4 dated 13 April 2007). This is a Windows Installer Package (a .msi file), and you can either run it directly from this webpage or download it to your computer and run it there.

If you want to see the Timer Utilities and Tasks components in action, you can download a couple of demo VB6 projects here. These include full source code and the executables (in case you don't have VB6). 

You are free to use these components for any purpose.

Source code is not provided. If you encounter any difficulties using these components, or have suggestions for improvement, please contact support@tradewright.com.

If you want to be kept informed about new versions of these components, please email me at support@tradewright.com, and I'll add you to my mailing list.

 

Timer Utilities Component

This component, called TimerUtils2, provides powerful facilities to enable programs to do time-related things. It provides a set of classes that can be used to do the following:

Note that TimerUtils2 is an enhancement to my earlier TimerUtils component, which you may have used (and which is still available here). TimerUtils2 has a simpler and more convenient programming interface than the earlier version, and is also very much more accurate in some areas. Programs written for the earlier TimerUtils will need minor modifications to work with TimerUtils2. Note however that TimerUtils and TimerUtils2 can coexist on the same machine, so installing TimerUtils2 does not require you to modify programs using the earlier version.

To use TimerUtils2, you need to set a reference in your program to TradeWright Interval Timer Utilities Version 2.

ElapsedTimer Class

This class uses Windows API facilities to measure elapsed times with great accuracy. It is a very simple class, having only two methods: the StartTiming method tells the ElapsedTimer object to start measuring time from the moment of the call; the ElapsedTimeMicroseconds method returns the number of microseconds that have elapsed since the last call to StartTiming.

Example usage:

Dim et As New ElapsedTimer
et.StartTiming
.
.
. (some processing)
.
.
MsgBox "Elapsed time is " & et.ElapsedTimeMicroseconds & " microseconds"

Note that this measures actual elapsed time, not simply the amount of processor time used by the process (another class for measuring processor time may be added in a future version). It may therefore include time spent while other threads are run due to the current thread being pre-empted, reaching the end of its timeslot, or yielding the processor.

IntervalTimer Class

This class uses Windows multimedia timers to provide precision notifications of when a specified interval has elapsed. These notifications are via the TimerExpired event. When you create an object of this class (using the global createIntervalTimer method), you specify when the first expiry notification should occur, and optionally an interval for repeated notifications after the first. You can also request that the notifications be generated at random intervals.

The first notification can be requested either as an elapsed number of seconds or milliseconds from the current time, or as an explicit date-and-time. The interval between subsequent notifications is always expressed in milliseconds.

The timing resolution can be as short as 1 millisecond, and the maximum interval is 2,147,483.647 seconds (nearly 25 days).

You use the StartTimer method to activate the timer, and the StopTimer method to stop it.

As an example, suppose you want to be notified when the time reaches 9:30 today, and every 5 minutes thereafter. First you need to declare a module-scope WithEvents variable in a class module:

Private WithEvents mTimer As IntervalTimer

You need to provide an event handler to catch the events generated by the timer:

Private Sub mTimer_TimerExpired()
.
process the event
.
End Sub

At some point, you need to instantiate the timer and set it going:

Public Sub myProc()
.
.
.
Set mTimer = createIntervalTimer(Date + CDate("10:00"), _
                              ExpiryTimeUnitDateTime, _
                              300000, _
                              False)
mTimer.StartTimer
.
.
.
End Sub

In the call to createIntervalTimer, the first argument specifies when the first notification is to occur. The second argument indicates how the first argument is specified, in this case as a date-time; the values for this argument must be a member of the ExpiryTimeUnits enum. The third argument specifies the interval in milliseconds between subsequent notifications, a value of zero indicating that there are no subsequent notifications. The last argument is set to True if you want the intervals between notifications to be random values in the range from 1 millisecond up to the value you specify for the first and third arguments.

Just a slight clarification: if you wanted the first notification 500 milliseconds from now, and subsequent notifications at 100 millisecond intervals, you'd create the timer like this:

Set mTimer = createIntervalTimer(500, _
                                                             ExpiryTimeUnitMilliseconds, _
                                                             100, _
                                                             False)

And if you wanted random notifications up to 2 seconds apart starting now, you'd do this:

Set mTimer = createIntervalTimer(2, _
                                                            ExpiryTimeUnitSeconds, _
                                                            2000, _
                                                            True)

In fact in this last case you could alternatively set the first argument to 0, indicating that there is no special first-time value.

And that's about all there is to it, except that you can use the StopTimer method at any time to stop a  timer.

TimerList Class

This class is very useful in situations where you have an unspecified number of 'things' that need attention at some future point in time that may be different for each 'thing'. You can't use an IntervalTimer object for each one, because you don't know how many of them to declare (actually there are ways around this, but it's much easier just to use the TimerList class if your timing requirements are not too exacting).

What the TimerList class does is to remember each of these 'things' and give it back to you when the specified time arrives. It does this by maintaining a list of TimerListItem objects, each of which has an expiry time and a data property which can hold any data you care to put into it. The TimerList object uses an internal IntervalTimer object which it uses to check periodically whether any of the TimerListItem objects have expired, and if so, it raises an ItemExpired event for each one. By handling that event and retrieving the item's data, you can then carry out whatever processing is required for the 'thing' that the data relates to.

As an example, suppose you have a set of fields on a form that change now and again (eg stock prices). You want to bring any change to the user's attention by setting the background colour of the field to red for half a second. First we need to declare a module-scope variable for the TimerList:

Private WithEvents mTimerList As TimerList

When we load the form, we'll instantiate the TimerList object, and we'll tell it to check for expired items every 250 milliseconds (this means it could be slightly more than half a second that a field remains highlighted, but that is acceptable).

Private Sub Form_Load()
.
.
.
Set mTimerList = createTimerList(250)
mTimerList.StartTiming
.
.
.
End Sub

Whenever a field is updated, we want to set the field's background colour to red and its foreground colour to white, and then add an item to mTimerList's list to expire after 500 milliseconds. Lets assume that the fields are a control array named PriceText. Then we can use the index of the relevant field as the data in the item that we add to the list.

Public Sub myProc()
.
.
.
PriceText(index).BackColor = vbRed
PriceText(index).ForeColor = vbWhite
mTimerList.Add index, 500, ExpiryTimeUnitMilliseconds
.
.
.
End Sub

We need to provide an event handler for the events generated by mTimerList. Here we'll retrieve the data from the expired item, which is the index of the field whose background and foreground colours need to be reset.

Private Sub mTimerList_ItemExpired(ByVal item As TimerListItem)
PriceText(CLng(item.data)).BackColor = vbWindowBackground
PriceText(CLng(item.data)).ForeColor = vbWindowText
End Sub

And there you have it: with about 10 lines of code, your dynamically updating fields now change colour for half a second each time they are updated!

Actually the observant among you may have noticed a bug in this example: if a second update for a field occurs while its colours are still changed from a previous update, then when the first item expires the colours will be reset, so the field won't remain highlighted for half a second after the second update. It's not very difficult to fix this, but I won't bother here as it will complicate the example unnecessarily.

 

Tasks Component

This component enables your program to work on one or more processor-intensive tasks while also handling normal events from user interface controls, COM components and so on. Each of these tasks gets a proportion of the available CPU time, so that they progress in parallel, and the proportion they receive depends on their priority. When a task completes, the program can receive an event indicating the completion.

Note that this is not multithreading - all of this happens on the normal VB6 single thread. It is therefore nowhere near as flexible and powerful as using multiple threads, but on the other hand it doesn't suffer from the complexities and problems that can plague multithreaded programs.

For some processing to be suitable as a candidate for use with Tasks, it should have the following characteristics:

  • it must be processor intensive: this means that it must not do things that give up control of the CPU, such as displaying a form or message box, or making a synchronous query to a database. If it does yield the processor, all other tasks in the process will also be suspended
  • it must be possible to identify convenient points in the processing where it can allow the Tasks mechanisms to decide whether to continue running the task or to run another task. Typically this will be the case when the proposed task does a lot of repetitive processing in a loop - the end of each iteration would be such a point where Tasks can intervene

As an example, TradeBuild (or rather the new StudiesSupport and ChartSupport component which have been separated out from TradeBuild in version 2.5) uses tasks when studies are added to tickers or composed on top of other studies. For example if you add a study to a chart that has 2000 bars, the study values must be calculated for each of those bars. Depending on the nature of the study, this could be quite time consuming, and without using a task no other events would be processed until the calculations were complete and the chart updated. By structuring the calculation process as a task, it can proceed in parallel with the normal event handling and the chart is effectively updated asynchronously.

By the way, you may be thinking that you can achieve the same effect by using DoEvents. This is true where there is only one such piece of processing. But consider this scenario: when your code hits DoEvents, an event is fired which causes another piece of lengthy processing to start: now even if this second piece of processing also uses DoEvents, the first piece will not resume until the second piece has completed, so they cannot proceed in parallel.

In contrast, Tasks allows multiple such processing tasks to progress in parallel. Not only that, but they can be prioritised so that more urgent processing is completed sooner than less urgent processing. Moreover, Tasks temporarily sets the thread scheduling priority to 'below normal' which prevents your heavy-duty processing making your system unresponsive (this default behaviour can be altered by setting a property)..

To use the Tasks component, you must set a reference in your project to TradeWright Task Services.

Using Tasks

The processing that you want to perform must be in a class module, and that class module must implement the Task interface. This interface has one property, TaskContext, and one method, Run.

The TaskContext property provides you with a reference to a TaskContext object, which you use to interact with the task mechanisms. When your processing is completed, you call the TaskContext object's finish method. Should you need to suspend your task for a while, you can call the Sleep method (other tasks will continue to be processed).

The Run method is invoked to let your class do a 'chunk' of processing: a chunk will typically be one iteration of your loop or some similarly convenient unit. When your code exits from the Run method, Tasks decides either to let your code continue processing (in which case it calls Run again) or to run another task (if there are any). It makes this decision based on how long your task has been running, and its priority.

As an example, suppose you have a class MyClass with a Process method that does some intensive processing that looks like this:

Public Sub Process(...arguments...)
.
.
perform some initialisation
.
.
Do While someCondition
   
.
    .
    do a chunk of processing
   
.
    .
Loop
End Sub

We want to transform this class into a task, so at the start of the module will add the following statement:

Implements Task

We'll need a reference to the TaskContext object:

Private mTC as TaskContext

We'll set that variable in our implementation of the Task interface's TaskContext property:

Private Property Let TaskContext(ByVal RHS As TaskContext)
Set mTC = RHS
End Property

Now we need to split the content of the Process method into two parts: the processing chunk will be done within the Task interface's Run method, but we'll still need to do the initialisation part before the task is set running, so we'll move this into a new Prepare method:

Public Sub Prepare(...arguments...)
.
.
perform some initialisation
.
.
End Sub

Private Sub Task_Run()
If Not someCondition Then
    mTC.finish
    Exit Sub
End If
 
.
.
.
perform a chunk of processing
.
.
.
End Sub

Note that we may need to make some minor adjustments. For example, if the original version of someCondition involved the values of one or more local variables, these variables will now have to become either Static or module-scope (the latter if they are also accessed in the initialisation code).

Now we need to set the task running. Instead of calling the original Process method, we now do the following:

Dim myTask As New MyClass
myTask.Prepare(...arguments...)
startTask myTask, PriorityNormal ' Note that startTask is a global method
.
.
carry on with other processing
.
.

The task will now run to completion in parallel with other processing. What do I actually mean by 'in parallel'? Well, like everything else in VB6, the Tasks component is event driven - it uses an IntervalTimer object to kick off task processing, so tasks will be processed whenever the program is not processing any other event. The maximum length of time a task will run before allowing other events to be processed is 16 milliseconds for a normal priority task, so it really doesn't get in the way too much. (Some care is needed though - if your basic unit of processing within the Run method takes, say, 100 milliseconds, there is nothing Tasks can do to reduce that.)

Now, with what we've done so far, there is no way for the code that started the task to know when it has finished, which in many cases it will need to know. The key to this is that the startTask method is actually a function, and it returns a TaskCompletion object. This object allows one or more clients to register with it, and when the relevant task has completed it notifies each of those clients (this notification also indicates whether the task completed as a result of a program error).

To register a client with a TaskCompletion object, you call its addTaskCompletionListener method, passing the client object as an argument. The client object itself must implement the TaskCompletionListener interface. This interface has only a single method, taskCompleted, which passes a user-defined type TaskCompletionEvent.

The TaskCompletionEvent has the following structure:

Public Type TaskCompletionEvent
    source     As Object  ' the task that has completed
    resultCode As Long    ' if non-zero, contains an error number
    message    As String  ' if resultCode is non-zero, contains
                          ' an error message
    data       As Variant ' the value of the data argument (if any)
                          ' passed to startTask
End Type

Now we can enhance the previous example. Let's assume that we want the class (which could of course be a form) that calls startTask to be informed of its completion. First we need to declare that this class is a TaskCompletionListener:

Implements TaskCompletionListener

Now we need to implement the taskCompleted method:

Private Sub TaskCompletionListener_taskCompleted(ev As TaskCompletionEvent)
If ev.resultcode <> 0 Then
    ' an error occurred while processing the task
    process the error
Else
    ' the task completed successfully
    process the successful completion
End If
End Sub

Finally we can update the code that starts the task. For purposes of illustration, we'll pass the value 12345 as the data argument of startTask to identify this particular task, but its value can be anything that makes sense in the context of the implementation (or it can be omitted if not needed):

Dim myTask As New MyClass
Dim tc As TaskCompletion
myTask.Prepare(...arguments...)
set tc=startTask(myTask, PriorityNormal, ,12345)
tc.addTaskCompletionListener Me
.
.
carry on with other processing
.
.

Now when the task completes or fails due to an error, the TaskCompletionListener object will call the TaskCompletionListener_taskCompleted method, and any necessary processing can be done.

 

Weak References Component

Quite often it is useful to link VB objects in a parent-child relationship, for example when the parent object manages the child object but also provides some method or property that the child object wants to use. This can of course be done very simply using a parent property in the child object which the parent sets when it creates the child. Thus, the parent has a reference to the child object and the child object has a reference to the parent object.

However this situation has a downside: VB releases an object (and the memory it consumes) only when there are no references to the object left. In the parent-child situation, neither object is ever released because there is always still one reference existing (ie the one from the other object). To prevent this, something else in the program has to specifically tell either the child or the parent to set its reference to the other object to Nothing. It's easy to not notice the need for this, or to fail to invoke this tidy-up mechanism, especially when the circularity arises from a complex chain of interconnected objects. The result is that memory that could be released is never freed, and if the number of objects involved increases with time, the amount of memory that the program consumes just keeps increasing - a classic memory leak.

The Weak References component provides a way around this. A WeakReference object holds a reference to another object, but it constructs the reference in such a way that VB is unaware of it: because of this, VB will not take that reference into account when deciding whether the other object has been finished with and can be released.

To use the Weak References component, set a reference in your project to TradeWright Weak Reference Services.

Using WeakReference Objects

To create a WeakReference object, call the global createWeakReference method. This method has a single argument which is the object that you want the weak reference to refer to.

When you want to access the referenced object, you use the WeakReference object's target property to set a local variable of the same type as the target object, and then access the object via this local variable.

For example, we might now implement our parent property on the child object as follows:

Private mParentRef As WeakReference ' module-scope weak reference variable
Public Property Let parent(ByVal value as ParentType)
Set mParentRef = createWeakReference(value)
End Property

To access the parent object, we now do the following:

Dim parent As ParentType
Set parent = mParentRef.target
parent.myMethod ...

Alternatively create a function that returns the proper reference to the target object:

Private Function getParent() As ParentType
Set getParent = mParentRef.target
End Function

Now you can access the parent like this:

getParent.myMethod ....

Note that you need to be a little careful with weak references. Before you access an object using a weak reference, you have to be sure that the object it is supposed to refer to still exists. This is a matter of correct design: there is no way of using a weak reference to check at run-time whether the target object still exists. If you do attempt to access an object that has been released in this way, your program will crash with a non-trappable error (if you are running the program within the VB development environment, the whole thing will crash).

The guideline here is that a weak reference should only be held to an object that is likely to persist longer than the object that holds the reference.

 

TimerUtils Component (superseded)

Please note that this component has been superseded by TimerUtils2. You are advised to use TimerUtils2 for all new work.

TimerUtils provides mechanisms for generating periodic events, for example to enable particular processing to be performed at regular intervals. It is an ActiveX DLL, not an ActiveX control, so you need to instantiate it in code rather than site it on a form.

TimerUtils uses Windows API functions to achieve high accuracy..

Using TimerUtils, you can create objects of class IntervalTimer and set various properties to determine the timer interval, and whether expiry of the interval is notified just once or repeatedly. For repeated notifications, you can specify an interval for the first notification that's different from the interval between subsequent notifications. You can also request notifications at random intervals, up to some specified maximum interval. You may create as many IntervalTimer objects as you want concurrently, to cater for the most complex timing requirements.

TimerUtils also has a TimerList class. Objects of this class maintain a list of TimerListEntry objects. A TimerListEntry object has a specified expiry time, and a data property. The data property can be set to refer to any object. When the expiry time has passed, an event is fired which contains the data as an argument. The application can then perform any required processing on this data. You create a TimerListEntry object by calling the TimerList object's AddTimerListEntry method. You set the frequency at which the TimerList object checks for expiry of TimerListEntry objects by means of its TimerIntervalMillisecs or TimerIntervalSecs properties.

Another class is ElapsedTimer. You can use objects of this class to measure elapsed time to a very high degree of accuracy. 

You can download TimerUtils from here. This is version 1.0.171.

When the download has completed, extract the contents of the zip file to a suitable folder and run the setup.exe program to install.

When you use the TimerUtils component in a VB6 or VBA project, include a reference to it so you can use the Object Browser to explore its classes, methods, properties and events. In the references dialog box it appears as 'TradeWright Interval Timer Utilities'.