Sub and Function Procedures
You've already been exposed to the event procedures that VB creates when you want to execute code in response to a user action (such as the Click event of a command button). You can also write programmer-defined procedures (Subs and Functions), which you would use not to respond to an event, but to modularize your code.
The difference between a Sub and a Function is that a Sub does not produce a return value (i.e., one that can be assigned directly to a variable, whereas a Function does produce a return value). Both Subs and Functions can be called with or without parameters.
To call a Sub, you use the Call statement. The statement below calls a Sub named "AddEm".
Call AddEm
This would cause VB to execute the statements in the Sub named "AddEm" and then return to the statement following the Call.
Private Sub AddEm()
[statements]
End Sub
The keyword "Call" is optional, thus a call can be made simply by coding the name of the Sub:
AddEm ' same as "Call AddEm"
Example (Calling a Sub Without Parameters)
In the "Try It" example below, upon clicking the "Try It" button, the user is prompted to enter two numbers. The Addem Sub is called, which performs the addition. Program flow then returns to the Print statement, which displays the sum of the two numbers on the form.
Note that the variables involved are declared at the module-level (i.e., general declarations section, prior to any Subs or Functions) because the variables are common to both the cmdTryIt_Click event procedure and the AddEm Sub procedure.
Code:
Option Explicit
' Module-level variables are needed for this example
Dim mintNum1 As Integer
Dim mintNum2 As Integer
Dim mintSum As Integer
. . .
Private Sub cmdTryIt_Click()
mintNum1 = Val(InputBox("Enter first number:", "Add Program"))
mintNum2 = Val(InputBox("Enter second number:", "Add Program"))
AddEm ' Could also be coded as "Call Addem()" with or without the ()
Print "The sum is "; mintSum
End Sub
Private Sub AddEm()
mintSum = mintNum1 + mintNum2
End Sub
Screen-shots of the run:
Download the VB project code for the example above here.
More often than not, a Sub procedure will be expecting one or more parameters. This enhances the reusability and flexibility of the Sub procedure. The full syntax for a Sub procedure is:
[Private | Public] Sub SubName[(parameter list)]
[statements]
End Sub
where "parameter list" is a comma-separated list of the parameter variables with their data types (i.e., var1 As datatype, var2 As datatype, etc.).
For example, if we were to modify the "AddEm" Sub to accept three Integer variables, the header for the "Addem" Sub would look like this:
Private Sub AddEm(pintNum1 As Integer, pintNum2 As Integer, pintSum As Integer)
The call to Addem must now specify, or pass, three integer variables to the Sub. The call can be coded in one of two ways. If the keyword Call is used, then the argument list (i.e., the set of variables that will be passed to the Sub) must be enclosed in parentheses:
Call AddEm(intNum1, intNum2, intSum)
If you omit the keyword Call, you must also omit the parenthesis surrounding the argument list:
AddEm intNum1, intNum2, intSum
Either variation of the syntax is acceptable, and one way is no more efficient than the other.
The important thing is that the argument list of the calling statement must match the parameter list of the Sub procedure one-for-one in terms of both the number of variables passed and the data types of the variables passed. The names of the corresponding argument/parameter variables can be (and often are) different – with the naming conventions I use, I start the variable names of the parameters in a Sub or Function header with the letter "p" for "parameter".
Example (Calling a Sub With Parameters)
In the "Try It" example below, as in the previous example, upon clicking the "Try It" button, the user is prompted to enter two numbers. The Addem Sub is called, which performs the addition. Program flow then returns to the Print statement, which displays the sum of the two numbers on the form.
The difference this time is that the three variables (the two addends and the sum) are passed as arguments to the Sub Addem. When the call is made, the Sub Addem adds pintNum1 and pintNum2, storing the result in pintSum. When program flow returns to the Print statement, the value of pintSum that was calculated in AddEm is available in its counterpart intSum.
The variables involved are declared at the local level (i.e., in the cmdTryIt_Click procedure, rather than in the general declarations section). A general rule-of-thumb is that variables should be as limited in scope as possible.
Code:
Option Explicit
. . .
Private Sub cmdTryIt_Click()
' The variables can be declared at the local level
Dim intNum1 As Integer
Dim intNum2 As Integer
Dim intSum As Integer
intNum1 = Val(InputBox("Enter first number:", "Add Program"))
intNum2 = Val(InputBox("Enter second number:", "Add Program"))
AddEm intNum1, intNum2, intSum ' Could also be coded as:
' Call Addem(intNum1, intNum2, intSum)
Print "The sum is "; intSum
End Sub
Private Sub AddEm(pintNum1 As Integer, pintNum2 As Integer, pintSum As Integer)
pintSum = pintNum1 + pintNum2
End Sub
When this example is run, it will behave exactly the same as the previous example.
Download the VB project code for the example above here.
Variables can be passed to a subroutine "by reference" or "by value". The default is "by reference". The differences are as follows:
Using our previous example, to explicitly pass the "input" variables (the two addends) by value and the "output" variable (the sum) by reference, you would modify the Sub procedure header as follows:
Private Sub AddEm(ByVal pintNum1 As Integer, _
ByVal pintNum2 As Integer, _
ByRef pintSum As Integer)
pintSum = pintNum1 + pintNum2
End Sub
No change would be made to the statement that calls the Sub.
To call a Function, the syntax is identical to an assignment statement that uses a VB built-in function:
VariableName = FunctionName[(argument list)]
For example, if I made a function called "AddEm" that returned a value (like the sum, for example), I could invoke the function as follows:
intSum = AddEm(intNum1, intNum2)
The above statement would cause the following to happen:
The format of the function procedure itself is:
[Private | Public] Function FunctionName[(parameter list)] [As datatype]
[statements]
FunctionName = value
[statements]
End Function
As when calling a Sub, the items passed in the argument list of the call to the Function must match the parameter list of the Function header in number and datatypes. The datatype of the return value must match the datatype of the target variable name in the assignment statement that calls the Function.
VB is "idiosyncratic" in the way it implements functions:
Ř In most languages that support functions (such as C, Java, Pascal, etc.), the return value is set in a "return" statement, such as
return(value);
Also, in most languages that support functions, control is returned to the calling statement as soon as the return value is set .
Ř On the other hand, with VB, the return value is set by assigning a value to the Function name within the Function's body:
FunctionName = value
Also, if executable statements are present after the assignment statement that sets the return value is set, those statements will be executed (i.e., setting the return value does NOT automatically cause control to be returned to the caller).
Following is the code to implement "AddEm" as a function:
Example (Calling a Function)
Option Explicit
. . .
Private Sub cmdTryIt_Click()
' The variables can be declared at the local level
Dim intNum1 As Integer
Dim intNum2 As Integer
Dim intSum As Integer
intNum1 = Val(InputBox("Enter first number:", "Add Program"))
intNum2 = Val(InputBox("Enter second number:", "Add Program"))
intSum = AddEm(intNum1, intNum2)
Print "The sum is "; intSum
End Sub
Private Function AddEm(pintNum1 As Integer, _
pintNum2 As Integer) _
As Integer
Addem = pintNum1 + pintNum2
End Function
When this example is run, it will behave exactly the same as the previous two examples.
Download the VB project code for the example above here.
Consider the following function, called IsOdd, which determines whether or not a number is odd:
Private Function IsOdd(pintNumberIn) As Boolean
If (pintNumberIn Mod 2) = 0 Then
IsOdd = False
Else
IsOdd = True
End If
End Function
Now consider the following section of code that calls this function:
Private Sub cmdTryIt_Click()
Dim intNumIn As Integer
Dim blnNumIsOdd As Boolean
intNumIn = Val(InputBox("Enter a number:", "IsOdd Test"))
blnNumIsOdd = IsOdd(intNumIn)
If blnNumIsOdd Then
Print "The number that you entered is odd."
Else
Print "The number that you entered is not odd."
End If
End Sub
Note that the Boolean variable blnNumIsOdd was used to hold the return value of the function, and was then subsequently used in an If statement. There's nothing wrong with doing this; however, there is a shortcut coding technique you can use with functions that return Boolean values. You can eliminate the "middle man" (the Boolean variable) by calling the function directly in the If statement. Following is the procedure shown above modified to use this technique:
Private Sub cmdTryIt_Click()
Dim intNumIn As Integer
intNumIn = Val(InputBox("Enter a number:", "IsOdd Test"))
If IsOdd(intNumIn) Then
Print "The number that you entered is odd."
Else
Print "The number that you entered is not odd."
End If
End Sub
Download the VB project code for the example above here.
Using Optional Arguments
VB allows you to create Subs or Functions that have optional arguments – i.e., arguments that may or may not be passed; and if not passed will assume a default value. Optional arguments are specified in the procedure header with keyword Optional. All Optional arguments must be placed at the end of the argument list (they must follow any required arguments). For example, the procedure header
Private Sub MySub(pblnFlag As Boolean, Optional plngNumber As Long)
specifies that the last argument is Optional. Any call to MySub must pass it at least one argument (a Boolean) in order to avoid an error. If the caller so chooses, a second argument (a Long) can also be passed to MySub. Consider the following calls to MySub:
MySub ' Error: Required argument is missing
MySub True ' OK – required argument is passed
MySub False, 10 ' OK – both required and optional arguments passed
Optional arguments that are not passed will take on default values. Numeric arguments will default to 0, Strings will default to "", Booleans will default to False, etc. So in the case of the second example above, the value of plngNumber would be 0.
You can explicitly set the value of an Optional argument by assigning it a value in the procedure header, using syntax like the following:
Private Sub MySub(pblnFlag As Boolean, Optional plngNumber As Long = 1)
In this case, if the argument for plngNumber is not passed, it will have the default value of 1; otherwise, it will have the value of whatever was passed. Default values can only be used with Optional arguments.
Following is an example using a Sub that takes in three Optional arguments with default values. The Sub calculates the gross pay of an employee:
Code:
Private Sub cmdTryIt_Click()
Print "Hours", " Base Rate", " O/T Rate", " Base Pay", " O/T Pay", " Gross Pay"
Print "-----", " ---------", " --------", " --------", " -------", " ---------"
CalcGrossPay ' Default all three arguments
CalcGrossPay 25 ' Default rightmost two arguments
CalcGrossPay 42, 30 ' Default rightmost argument
CalcGrossPay 43, 28, 1.1 ' Pass all three arguments
CalcGrossPay 40, , 1 ' Default second argument
CalcGrossPay , 37 ' Default first and third arguments
CalcGrossPay , , 1.25 ' Default first two arguments
End Sub
Private Sub CalcGrossPay(Optional pintHoursWorked As Integer = 40, _
Optional psngBaseHourlyRate As Single = 35, _
Optional psngOvertimeRate As Single = 1.5)
Dim sngBasePay As Single
Dim sngOvertimePay As Single
Dim sngGrossPay As Single
If pintHoursWorked <= 40 Then
sngBasePay = psngBaseHourlyRate * pintHoursWorked
sngOvertimePay = 0
Else
sngBasePay = psngBaseHourlyRate * 40
sngOvertimePay = (pintHoursWorked - 40) _
* psngBaseHourlyRate _
* psngOvertimeRate
End If
sngGrossPay = sngBasePay + sngOvertimePay
Print pintHoursWorked, _
Format$(Format$(psngBaseHourlyRate, "Fixed"), "@@@@@@@@@@"), _
Format$(Format$(psngOvertimeRate, "Fixed"), "@@@@@@@@@@"), _
Format$(Format$(sngBasePay, "Currency"), "@@@@@@@@@@"), _
Format$(Format$(sngOvertimePay, "Currency"), "@@@@@@@@@@"), _
Format$(Format$(sngGrossPay, "Currency"), "@@@@@@@@@@")
End Sub
If you build this example on your own, make sure your form is wide enough to display all columns.
Screen-shots of the run:
Download the VB project code for the example above here.
The following example demonstrates the use of the IsMissing function, which can be used to test if an Optional Variant argument has been passed to a procedure (the IsMissing function will not work properly with non-Variant Optional arguments). This example produces the same output as the previous example:
Private Sub cmdTryIt_Click()
Print "Hours", " Base Rate", " O/T Rate", " Base Pay", " O/T Pay", " Gross Pay"
Print "-----", " ---------", " --------", " --------", " -------", " ---------"
CalcGrossPay ' Default all three arguments
CalcGrossPay 25 ' Default rightmost two arguments
CalcGrossPay 42, 30 ' Default rightmost argument
CalcGrossPay 43, 28, 1.1 ' Pass all three arguments
CalcGrossPay 40, , 1 ' Default second argument
CalcGrossPay , 37 ' Default first and third arguments
CalcGrossPay , , 1.25 ' Default first two arguments
End Sub
Private Sub CalcGrossPay(Optional pvntHoursWorked As Variant, _
Optional pvntBaseHourlyRate As Variant, _
Optional pvntOvertimeRate As Variant)
Dim sngBasePay As Single
Dim sngOvertimePay As Single
Dim sngGrossPay As Single
If IsMissing(pvntHoursWorked) Then pvntHoursWorked = 40
If IsMissing(pvntBaseHourlyRate) Then pvntBaseHourlyRate = 35
If IsMissing(pvntOvertimeRate) Then pvntOvertimeRate = 1.5
If pvntHoursWorked <= 40 Then
sngBasePay = pvntBaseHourlyRate * pvntHoursWorked
sngOvertimePay = 0
Else
sngBasePay = pvntBaseHourlyRate * 40
sngOvertimePay = (pvntHoursWorked - 40) _
* pvntBaseHourlyRate _
* pvntOvertimeRate
End If
sngGrossPay = sngBasePay + sngOvertimePay
Print pvntHoursWorked, _
Format$(Format$(pvntBaseHourlyRate, "Fixed"), "@@@@@@@@@@"), _
Format$(Format$(pvntOvertimeRate, "Fixed"), "@@@@@@@@@@"), _
Format$(Format$(sngBasePay, "Currency"), "@@@@@@@@@@"), _
Format$(Format$(sngOvertimePay, "Currency"), "@@@@@@@@@@"), _
Format$(Format$(sngGrossPay, "Currency"), "@@@@@@@@@@")
End Sub
Download the VB project code for the example above here.
The following example demonstrates the use of the named arguments, where you can specify the arguments to be passed in the calling statement using the syntax
ArgumentName:=value
Using named arguments, the arguments to the procedure can be passed in any order. All required arguments must be passed, but optional arguments can be omitted if desired. Using named arguments saves coding when calling a procedure that has a lot of optional arguments but you only need to pass a few of them.
Following is the code for BoxVolume program using named arguments:
Private Sub cmdTryIt_Click()
Print "Hours", " Base Rate", " O/T Rate", " Base Pay", " O/T Pay", " Gross Pay"
Print "-----", " ---------", " --------", " --------", " -------", " ---------"
CalcGrossPay
CalcGrossPay pintHoursWorked:=25
CalcGrossPay pintHoursWorked:=42, psngBaseHourlyRate:=30
CalcGrossPay pintHoursWorked:=43, psngBaseHourlyRate:=28, psngOvertimeRate:=1.1
CalcGrossPay pintHoursWorked:=40, psngOvertimeRate:=1
CalcGrossPay psngBaseHourlyRate:=37
CalcGrossPay psngOvertimeRate:=1.25
End Sub
Private Sub CalcGrossPay(Optional pintHoursWorked As Integer = 40, _
Optional psngBaseHourlyRate As Single = 35, _
Optional psngOvertimeRate As Single = 1.5)
Dim sngBasePay As Single
Dim sngOvertimePay As Single
Dim sngGrossPay As Single
If pintHoursWorked <= 40 Then
sngBasePay = psngBaseHourlyRate * pintHoursWorked
sngOvertimePay = 0
Else
sngBasePay = psngBaseHourlyRate * 40
sngOvertimePay = (pintHoursWorked - 40) _
* psngBaseHourlyRate _
* psngOvertimeRate
End If
sngGrossPay = sngBasePay + sngOvertimePay
Print pintHoursWorked, _
Format$(Format$(psngBaseHourlyRate, "Fixed"), "@@@@@@@@@@"), _
Format$(Format$(psngOvertimeRate, "Fixed"), "@@@@@@@@@@"), _
Format$(Format$(sngBasePay, "Currency"), "@@@@@@@@@@"), _
Format$(Format$(sngOvertimePay, "Currency"), "@@@@@@@@@@"), _
Format$(Format$(sngGrossPay, "Currency"), "@@@@@@@@@@")
End Sub
Download the VB project code for the example above here.
If you need to exit a Sub or Function procedure "early", you can use the Exit Sub or Exit Function statements, respectively.
Example:
Private Sub cmdTryIt_Click()
Dim intNumIn As Integer
intNumIn = Val(InputBox("Enter a non-zero number:", "Test"))
If intNumIn = 0 Then
Print "You entered zero or a non-numeric value."
Exit Sub
End If
Print "The number you entered was: "; intNumIn
End Sub