Creating the CInput ActiveX component
The CInput component will be used to request an upper value for the logarithm and factorial tables computed by our application. In this section we’ll illustrate one of several techniques that can be used to wrap a class around a Visual Basic form. Start by creating a new form and changing its name to FormInputData. Set the BorderStyle property of the form to Fixed Dialog, so the form only has a Close Window icon on the title bar. Add two label controls, one text box control and two command button controls. Make sure the property CausesValidation is set to true for all the controls. Change the names of the controls and lay them out in the form, as illustrated in Figure 11.
Now, add the code shown in Sample Code 7 to the form:
Let’s see what all these lines of code do. First notice that the form has three variables that are declared public: MinValue, MaxValue and OpCanceled. These variables are declared public so the class wrapping the form can access them. MinValue and MaxValue set the minimum and maximum values that can be entered in the text box txtInputValue. You don’t want your user to ask the program to compute the factorial of 1000, do you?.
Variable OpCanceled is used to store the button clicked by the user. If the user clicks on cmdCompute, then OpCanceled is set to false; if the user clicks on button cmdCancel, then OpCanceled is set to true. This is done in the procedures that handle events cmdCancel_Click and cmdCompute_Click. Note that these events also hide the form (with a call to Me.Hide), so it disappears from the screen.
We also have a procedure to handle the event QueryUnload:
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
If UnloadMode = vbFormControlMenu Then
Cancel = True
OpCanceled = True
Me.Hide
End If
End Sub
This event is fired when the user clicks the X icon on the title bar to close the form. You will notice that this event passes an argument, Cancel, to the event handler. Setting this argument to true stops the form from closing (and unloading). Instead, we merely set the Visible property of the form to false (calling Me.Hide), which removes the form from the screen without unloading it. Also note that when the user tries to close the form this way the program considers it to be equivalent to clicking on the Cancel button, because variable OpCanceled is set to true.
The next procedure in this form is a handler for the Validate event generated by the text box control txtInputValue. This event is generated when the user tries to leave the control, and we use it to ensure that the value that has been entered falls within the appropriate range and that it is a number:
Private Sub txtInputValue_Validate(Cancel As Boolean)
On Error GoTo ErrorHndl
'Check that value entered is between the limits
If Int(txtInputValue.Text) < MinValue Or _
Int(txtInputValue.Text) > MaxValue Then
MsgBox "You must enter a value between " & MinValue & _
" and " & MaxValue
Cancel = True
End If
Exit Sub
ErrorHndl:
MsgBox "The value you entered is invalid"
Cancel = True
End Sub
If the value entered by the user is not a number, the operation int(txtInputValue) will generate an error that is trapped by the On Error Goto ErrorHndl instruction. In this case the user gets a generic message box stating that the entered value is not valid. If the value is a number that is not in the valid range then the user would get a message indicating the valid range for the number.
Ok, that’s all for the form. Now lets take a look at the class we can build to wrap this form into an ActiveX object (Sample Code 8). I can hear you saying this is a heck of a lot of code compared to the other classes, and you are right. But after a second look you’ll also notice that the code is very simple. Let’s take it to the operating room and start dissecting:
First we have the initialization code for the class:
Private Sub Class_Initialize()
'Create the form and initialize its properties to default values
Set mvarfrmInput = New FormInputData
mvarfrmInput.MinValue = 1
mvarfrmInput.MaxValue = 10
mvarfrmInput.txtInputValue.Text = 10
mvarLastValue = mvarfrmInput.txtInputValue.Text
mvarfrmInput.OpCanceled = False
End Sub
All we do here is initialize the private class variable mvarfrmInput with a new instance of FormInputData and then set the form variables to default values. We also set the value of the private variable mvarLastValue to the current contents of txtInputValue in the form.
The code to terminate the class merely unloads the form from memory and sets the variable containing the private instance of the form to nothing.
Private Sub Class_Terminate()
Unload mvarfrmInput
Set mvarfrmInput = Nothing
End Sub
Most of the rest of the code just defines properties for the class. These properties are nothing more than wrappers to properties in the form, and except for property OpCanceled, which is read only, all the properties allow a client program to read or change the contents of the different controls in the form. For example, the class property InputValue can be used to set the contents of the text control txtInputValue in form FormInputData before the form is displayed to the user. Once the user has entered a value, this same class property can be used to get the value entered by the user.
The CInput class has only one method; Show:
Public Sub Show()
mvarLastValue = mvarfrmInput.txtInputValue.Text
mvarfrmInput.Show vbModal
If mvarfrmInput.OpCanceled Then
mvarfrmInput.txtInputValue.Text = mvarLastValue
End If
End Sub
This method displays the form and waits until the user clicks either of the form’s buttons or closes the form. If the user clicks the cmdCancel button or closes the form using the window title icon, then the value in txtInputValue is discarded and replaced with whatever value the control had before the form was displayed.
To close the discussion on this class, it is important to notice that with this technique, the wrapped form is loaded by the class when the class is initialized, and the form is never unloaded until the class instance is terminated. That’s why we put all that code in the form to stop it from unloading, even when the user thinks the form is being unloaded (i.e. when they click the Close Window icon on the title bar). Instead, we create the illusion that the form is being unloaded by just hiding it from the desktop.
Ok, I know you are anxious to try this new class with a Fortran program. So let’s write another simple Fortran subroutine that uses this class. Again, we’ll add this subroutine to our f90VBGUIUtils module (Sample Code 9). You might have noticed that, unlike the other procedures we have seen before, this subroutine receives as an argument a variant variable containing a reference to the CInput object. We do this because we expect this to be an object that is used frequently by the user, so we don’t want to go through the process of creating/destroying the object each time. So we will do this only once, at the top application level. For convenience, variable CInput is declared in module f90VBGUIUtils rather than in the main application. Sample Code 10 has a simple main program (TestGetInputValue) you can use to test this subroutine, and below you’ll find links explaining how to compile and link this program using several compilers:
Instructions to compile TestGetInputValue with Absoft Pro Fortran
Instructions to compile TestGetInputValue with Compaq Visual Fortran
Instructions to compile TestGetInputValue with Lahey Fortran 95
Figure 12 shows a screen shot of the running test program when an erroneous value has been entered.
So far you have already seen several techniques to create ActiveX objects that can then be accessed from your Fortran programs using f90VB. The next section describes the ActiveX object that will handle the main screen of our Fortran application.
Creating the CMain ActiveX component