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.

 

Calling a Sub 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.

 

 

Calling a Sub With Parameters

 

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.

 

      

ByRef vs. ByVal

 

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.

 

Calling a Function

 

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.

 

 

Functions That Return Boolean Values

 

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.

 

Using Named Arguments

 

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.

 

 

Exiting a Sub or Function Early

 

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