Creating a Progress-Indicator Component

by Michael C. Amundsen

If you've ever had to write an application involving at least one process that took lots of time, you know how important it is to create some type of progress indicator to let users know the program hasn't locked up. In this article, we'll show you how to use Visual Basic 4.0-32 or Visual Basic 5.0 to build a progress-indicator component that you can use in all your future projects.

Along the way, we'll also show you how to use the new Visual Basic 5.0 keyword WithEvents to create an event sink from a DLL instead of an ActiveX control. Using WithEvents allows you to create an asynchronous callback from your component back to the application that's using the component.

Not an ActiveX control

To design and implement a reusable component with Visual Basic, you must first determine the final product. Will it be an ActiveX control, a DLL, or an EXE? Although your initial reaction might be to use an ActiveX control, this isn't always the best choice—for several reasons. First, you can only build ActiveX controls using Visual Basic 5.0. If you're using VB4, you must choose between a DLL or an EXE component. Second, if you're using Visual Basic 4.0-16, you can't build a DLL component—only an EXE. Table A shows a quick guide to component-building options with Visual Basic 4.0 and 5.0.

Table A: Component options with Visual Basic 4.0 and 5.0

Component VB4-16 VB4-32 VB5
OCX No No Yes
DLL No Yes Yes
EXE Yes Yes Yes

Another consideration is that DLLs are often easier to work with than ActiveX controls. For example, what if you wanted to use this progress indicator but didn't have a default form in your application? Finally, VB4 won't allow you to display a modeless dialog box within an OLE DLL. Since our progress-bar component requires the display of such a dialog, you should select the EXE option when building this component in VB4; if you're using VB5, choose the ActiveX DLL option.

Planning the project

Once you've determined the component type, you need to design the interface between the object and your applications. The object interface describes the properties and methods the component will "publish" to other applications. If you're using VB5, you can also create event messages that your component can send back to the application.

Since we want to use this progress indicator in lots of different situations (copying files, calculating totals, loading records into memory, etc.), we'll build a very generic tool. Also, since our goal is to focus on the process of building and using the component, we'll keep the design simple.

We'll use three values to control updating the progress bar. We'll assume that the progress bar starts at zero (MinValue) and ends at 100 (MaxValue). However, users will be able to set each value at any value they choose. As the process progresses, the Value property will be updated. It's this Value property that will be represented on the dialog box's progress bar.

There are only three methods defined for the component. The Show method will be invoked at the start of the process. Each time users want to update the progress value, they can use the Update method. Finally, when the work is finished, the Drop method will remove the dialog from the screen.

We're making one big assumption in our component—that there'll be a dialog onscreen. That means we must build a form. Figure A shows what this form looks like in the VB IDE.

Figure A: Our progress dialog form will look like this.

Building the dialog form

Now that the design is done, it's time to build the component. If you're using Visual Basic 5.0, start Visual Basic and select ActiveX DLL as the project type. If you're using Visual Basic 4.0, just start Visual Basic with the default project.

We'll begin by setting up the dialog form. If you're using Visual Basic 5.0, you'll first need to add a blank form. To do this, choose Add Form from the Project menu. In the dialog box that appears, choose Form from the New page and click OK.

Next, you'll need to make the ProgressBar control available for your project. In VB5, you'll select Components… from the Project menu, click the check box beside Microsoft Windows Common Controls 5.0 OCX and click OK. To access the Components dialog box in VB4, select Controls… from the Tools menu. The ProgressBar component should now appear in the Toolbox.

Add a ProgressBar and a Label component to the form, as shown in Figure A. Next, set the form and component properties as Table B indicates. Don't worry yet about the Caption properties of the form and label; we'll change those at runtime.

Table B: Dialog-form elements

Object Property Setting
Form Name
BorderStyle
ControlBox
StartUpPosition
FrmProgress
3 - Fixed Dialog
False
2 - Center Screen (VB 5VB5 Only)
ProgressBar Name PrgProgress
Label Name LblProgress

If you're using Visual Basic 4.0-32, you'll need to add an extra bit of code to the form. Listing A shows the code to add to the Form_Load event. This code will center the form on the screen. In VB5, forms have the new StartupPosition property, so you needn't use this code.

Listing A: Code to center form in VB4

Private Sub Form_Load()

    ' for VB4 only

    With Me
        .Left = (Screen.Width - .Width) / 2
        .Top = (Screen.Height = -.Height) / 2
    End With

End Sub

After you've laid out the dialog form, save it as frmProgress.frm and the project as Progress. In VB5, you'll also be prompted to save the class module; name it prgDialog.cls.

Creating the object interface

Next, we'll write the code for the object interface that will use the form. In this step, we'll create the properties and methods that define the object interface.

If you're using Visual Basic 4.0-32, add a class module to the project by choosing Class Module from the Insert menu. (VB5 creates the module automatically.) Next, set the Name property of the class object to prgDialog and save it as prgDialog.cls. You'll use this class object name when you call the component from other programs.

Finally, we must make sure that the class module is visible to other programs outside the DLL/EXE component. To do this in VB5, set the Instancing property to 2 - Creatable MultiUse. In VB4, set the Instancing property to Multiuse and the Public property to True.

Now we need to create local storage variables for each property in Table A. Section 1 of Listing B shows the code to add to the General Declarations section of the class module.

Listing B: Progress-component code

Option Explicit

Private intMinValue As Integer
Private intMaxValue As Integer
Private intValue As Integer
Private strCaption As String
Private strTitle As String

--------------------------------

Private Sub Class_Initialize()
    
    ' Set initial values.
    
    strCaption = "Working..."
    strTitle = "One Moment..."
    intMinValue = 0
    intMaxValue = 100
    intValue = 0
    
End Sub

--------------------------------

Public Property Get DialogTitle() As String
    DialogTitle = strTitle
End Property

Public Property Let DialogTitle(ByVal vNewValue As String)
    strTitle = vNewValue
End Property

Public Property Get DialogCaption() As String
    DialogCaption = strCaption
End Property

Public Property Let DialogCaption(ByVal vNewValue As String)
    strCaption = vNewValue
End Property

Public Property Get MinValue() As Integer
    MinValue = intMinValue
End Property

Public Property Let MinValue(ByVal vNewValue As Integer)
    intMinValue = vNewValue
End Property

Public Property Get MaxValue() As Integer
    MaxValue = intMaxValue
End Property

Public Property Let MaxValue(ByVal vNewValue As Integer)
    intMaxValue = vNewValue
End Property

Public Property Get Value() As Integer
    Value = intValue
End Property

Public Property Let Value(ByVal vNewValue As Integer)
    ' read-only
End Property

--------------------------------

Public Sub Show()
    
    UpdateDialog
    frmProgress.Show
    
End Sub

Public Sub Update(intTemp As Integer)
    
    intValue = intTemp
    UpdateDialog
    
End Sub

Public Sub Drop()
    
    Unload frmProgress
    
End Sub

--------------------------------

Private Sub UpdateDialog()
    
    ' Update the form.
    
    With frmProgress
        .lblProgress = strCaption
        .Caption = strTitle
        
        With .prgProgress
            .Min = intMinValue
            .Max = intMaxValue
            .Value = intValue
        End With
        
    End With
    
    DoEvents ' yield for screen refresh
    
End Sub

After creating the local storage variables, add the code in section 2 of Listing B to the Class_Initialize event. This code will execute each time a user creates an instance of the object.

Now you're ready to create the property routines for the object. Using Table C as a reference, add the five properties to the object.

Table C: Component properties and methods

Item Name Type Default
Property DialogTitle
DialogCaption
intMinValue
intMaxValue
intValue
String
String
Integer
Integer
Integer
"One Moment"
"Working…"
0
100
0
Method Show
Update
Drop

Adding these properties creates a pair of routines for each property. (Property Get and Property Let). The Get routine allows users to "get" the local storage value associated with the property. The Let routine "lets" users place a value into the associated local storage location. These routines simplify the basic coding of the properties. Section 3 of Listing B shows the coding needed for all five Property Let and Property Get statements. Note that you must change each property's type as indicated in Table C.

As you can see, the only code needed for each property is a line that handles the association between the local storage and the published property name. Note that the Property Let statement for the Value property has only a comment line. This property will be read-only for users, so there's no code to allow users to place data in the local storage associated with that property.

Coding the object methods

The final step in completing the object interface is to code the object methods. Table C describes three methods (Show, Update, and Drop). Add these methods as public subs and insert the code from section 4 of Listing B.

We also need a method to handle some of the "undercover" work of the object. Add the UpdateDialog method to the project as a private sub, using the code from section 5.

The job of the UpdateDialog method is to move the local storage values onto the form and the progress-bar control. Notice the use of the DoEvents keyword, which forces the operating system to pause a moment to allow the screen to refresh.

As you can see, the three methods are quite simple. Note the use of the parameter for the Update method. That's all the code you need for a basic progress bar.

If you're using Visual Basic 4.0-32, you can skip to "Compiling the Component," below. In VB5, however, you can add an event to your component object.

Adding the complete event

One of the new features of Visual Basic 5.0 is the ability to add event messages to your programs. Most people know that you can add events to ActiveX controls, but you can also add events to DLL components. In this example, you'll add a message that fires each time the progress bar reaches completion.

There are two steps to creating event messages: declaration and invocation. First, you must add code to the General Declarations sections of the object. To do so, add the following lines of code:

' vb5 only
Public Event Completed()

Now you need to add code that "fires off" the event message. Do this by changing the Update procedure as follows. (Add the code shown in color.)

Public Sub Update(intTemp As Integer)
    
    intValue = intTemp
    UpdateDialog
    
    ' vb5 only
    If intValue >= intMaxValue Then
        RaiseEvent Completed
        Unload frmProgress
    End If
    
End Sub

The new code first checks to see if the maximum progress value has been reached. If so, it sends the Completed message and unloads the form.

Compiling the component

Now that the coding is done, you're ready to compile the component for use in other programs. If you're using Visual Basic 4.0-32, first insert a BAS module by selecting Module from the Insert menu. Then, insert a single, empty procedure called Main. This step is necessary in order for the operating system to register the component for use on the workstation. The Visual Basic 5.0 ActiveX DLL template takes care of this process automatically.

Choose Options… from the Tools menu and click the Project tab. Type Progress in the Project Name field, and choose Sub Main from the Startup Form dropdown list. Click OK.

Now you're ready to compile the object. In Visual Basic 4.0-32, select Make EXE File… from the File menu. In VB5, choose Make Progress.dll… from the File menu; accept the default name (Progress). Once the component has compiled, we'll create a simple test project to see how it works.

Testing the progress indicator

Now that the component is completed, you're ready to test it out. Save all your files, then start a new standard Visual Basic project. Add a single button to the default form. Set the button's Name to cmdTest and its Caption to Test. Now, add the code in Listing C to the cmdTest_Click event.

Listing C: cmdTest_Click() code

Private Sub cmdTest_Click()
    
    Dim objProgress As Object
    Dim x As Integer
    Dim y As Integer
    
    Set objProgress = CreateObject("Progress.PrgDialog")
    
    objProgress.Show
    
    For x = 1 To 100
        objProgress.Update x
        For y = 1 To 10000
            ' na
        Next
    Next
    
    objProgress.Drop
    
    Set objProgress = Nothing
    
End Sub

This is all the code you need to test the new object. Note the use of CreateObject() to make the link between this application and the component. This method of linking occurs at runtime and is called late binding of the object. There's another version called early binding that you can use at design time, as we'll see shortly.

To try the project out, run it and press the Test button. You should see a progress bar like the one in Figure B.

Figure B: Our test project brings up this progress dialog box.

(If you have trouble running your test project, be sure you've spelled the project name and class name correctly in the CreateObject() method. Also, check that you've compiled the DLL/EXE component before running the test code. Compiling the component automatically updates the Windows Registry with information about your component—without this information, VB won't be able to locate and use the Progress component.)

Now add a second button called cmdEvent with a caption of Event. We'll use this button to test the early-binding method of component use. (If you're using VB5, you'll get to test the event message, too.)

First, create a reference to the component in your project, thus binding your project to the component. To do this in VB5, select References… from the Project menu. Now, locate and select the Progress component from the list, and click OK. In VB4, you access the References dialog box by selecting References… from the Tools menu.

When you have a reference to the component in your project, you can declare the local variable using the object name instead of the generic As Object designation. Finally, in order to receive event messages, Visual Basic 5.0 requires an expanded declaration statement for the object that references the DLL. Listing D shows the proper declaration for Visual Basic 4.0-32 and 5.0. Add the appropriate line to the General Declarations section of your test project's code.

Listing D: Early binding code

Option Explicit

' vb5 only
Dim WithEvents prgBar As PrgDialog
' vb4 version
'Dim prgBar As PrgDialog
'

Now you're ready to add code to the cmdEvent_Click event. The code in Listing E looks quite similar to the code in Listing C.

Listing E: cmdEvent_Click code

Private Sub cmdEvent_Click()
    
    Dim x As Integer
    Dim y As Integer
    
    Set prgBar = New PrgDialog
    
    prgBar.DialogCaption = "Counting..."
    prgBar.DialogTitle = "Event Version"
    
    prgBar.Show
    
    For x = 1 To 100
        prgBar.Update x
        For y = 1 To 10000
            ' na
        Next
    Next
    
    prgBar.Drop
    
    Set prgBar = Nothing
    
End Sub

If you're using VB5, you can now add code to the prgBar_Completed event. This code is shown in Listing F.

Listing F: prgBar_Completed() code

Private Sub prgBar_Completed()
    
    ' handling an event message from DLL in vb5
    
    Beep
    MsgBox "Task Completed!"
    
End Sub

That's all there is to it. Now, when you run the project under Visual Basic 5.0 and press the Event button, you'll see the same progress bar plus a dialog box that pops up at the end of the loop, as shown in Figure C.

Figure C: The event message pops up to indicate that the process is complete.

Conclusion

A progress bar serves as an important indicator that an application hasn't frozen up during a lengthy process, such as copying data. In this article, we've shown you how to create a component to display a progress indicator in your 32-bit VB applications.

Copyright © 1998, ZD Inc. All rights reserved. ZD Journals and the ZD Journals logo are trademarks of ZD Inc. Reproduction in whole or in part in any form or medium without express written permission of ZD Inc. is prohibited. All other product names and logos are trademarks or registered trademarks of their respective owners.