Modules and Procedures

All executable statements in Visual Basic must belong to a procedure. A procedure is a block of code enclosed between a Sub . . . or Function . . . statement and a matching End Sub or End Function statement. Once you have written a procedure, it can be called by a program statement (sometimes referred to as the calling code) somewhere else in your program. This is known as making a procedure call. When the procedure has finished executing, it will (usually) return control to the procedure that called it (the calling procedure).

A procedure contains all of the code necessary to carry out a particular task. Once we have created the procedure, we can call it every time we need to carry out that task. Procedures thus reduce the total amount of code we need to write in order to implement a program. Procedures also allow us to construct our program from a number of discrete building blocks, each of which contains only the code it needs to achieve its allotted task and communicate with other parts of the program. Furthermore, because a procedure has such a specific role, it is easy to determine whether the procedure's code is working as intended. Procedures thus facilitate the testing and debugging of programs.

The applications we have created so far have all had a single form, and all of the code for the application has been contained within the form's class definition. For relatively small-scale applications like the ones featured in these pages, that's perfectly acceptable. For larger applications, which may require thousands or even tens of thousands of lines of code in order to implement a program, the task of updating and maintaining the program code can rapidly become unmanageable unless we can impose some kind of structure on it - which is where modules come in.

A module is a file that contains a number of related procedures. Just as a procedure groups together the program instructions required to carry out a particular task, a module groups together procedures that carry out closely related tasks. For example, we could group together procedures that carry out file-handling tasks in one module, and procedures dedicated to manipulating graphic images in various ways in another module.

It is often the case that procedures written for one application can be used in another application with little or no modification. If these procedures are placed in a separate module, they can easily be imported into any project that needs them. The ability to reuse code in this way represents the potential to significantly reduce the amount of time and resources required to complete a project. In this page we will be taking a somewhat closer look at procedures. Before we do that, let's create a Visual Basic module.

  1. Create a new project called "Modules"
  2. Change the Text property of the main form to "Geometry"
  3. Change the name of the form to "frmGeometry"
  4. Set the application's Startup Object property to Modules.frmGeometry.
  5. Add controls to the form as shown below (we have used a form size of 300 x 240)

The frmGeometry form

Your form should look like this screenshot


  1. Change the names of the controls as specified in the table below

Form Controls
ControlName
FormfrmGeometry
TextBox 1txtSide
TextBox 2txtPerimeter
TextBox 3txtArea
Button 1cmdPerimeter
Button 2cmdArea
Button 3cmdExit

The form's code module is created when the form is created, and is where the form's code will usually be found. On this occasion, however, we are going to create a separate code module to hold re-usable procedures. To create a module:

  1. Click on Project Add Module . . . . You will see the following dialogue box:

The Add New Item ► Modules dialogue box

The Add New Item Modules dialogue box


  1. Change the default filename to "modGeometry.vb", and click Add. The code editor window will open and display the new module's code window. Any code you create here will be available to other parts of your project, and the module itself will appear in the Solution Explorer window.

Subroutines

The procedures we use fall into two general categories: those that are provided by the Visual Basic programming language, and those we create ourselves. Beyond this distinction, however, there are two quite different kinds of Visual Basic procedure - subroutines and functions. So far, we have not really discussed the differences between these different kinds of procedure in any detail.

We'll talk about subroutines first, because nearly all of the procedures we have created in our example programs up to now have been subroutines. A subroutine is created by typing the Sub keyword, followed by a procedure name (which should be chosen to reflect the purpose of the subroutine), followed by parentheses. To end the subroutine, we type the keywords End Sub. The general syntax of a subroutine declaration is shown below, and should be familiar to you.

Sub ProcedureName()
  .
  .
  .
End Sub

A widely used convention for naming subroutines (and procedures in general) is to start the name of the subroutine with a capital letter. If a combination of words is used, each new word should begin with a capital letter, e.g. CalculatePrice, or PrintRecord. The code that is placed between the Sub and End Sub statements is called the body of the subroutine, and defines what the subroutine actually does. The variables required by a subroutine - there can be any number of these - can be declared within the body of the subroutine as follows:

Sub ProcedureName()
  Dim strFirstName As String
  strFirstName = "Fred"
End Sub

The actions performed by the subroutine will depend on the program statements that make up the body of the subroutine. The above procedure simply declares a string variable called strFirstName, and assigns it the value "Fred". Let's create a couple of subroutines for our Geometry application. We'll place these subroutines in our newly created modGeometry module.

  1. In the module's code editor window (between Module modGeometry and End Module), enter the following code:

Sub SquarePerimeter()
  Dim dblSide As Double
  Dim dblPerimeter As Double
  dblSide = frmGeometry.txtSide.Text
  dblPerimeter = dblSide * 4
  frmGeometry.txtPerimeter.Text = dblPerimeter
End Sub

Sub SquareArea()
  Dim dblSide As Double
  Dim dblArea As Double
  dblSide = frmGeometry.txtSide.Text
  dblArea = dblSide * dblSide
  frmGeometry.txtArea.Text = dblArea
End Sub

When we use a procedure, we are said to be calling the procedure. To call a subroutine, we type its name in the section of code where it is to be used. We are now going to call the subroutines defined in the modGeometry module from the application's form:

  1. Open the form designer window and double-click the Calculate Perimeter button. The code editor window for the form will open and you will see that the following code has been created:

Private Sub cmdPerimeter_Click(sender As Object, e As EventArgs) _
    Handles cmdPerimeter.Click

End Sub

  1. In the body of the subroutine, type "SquarePerimeter()"
  2. Now go back to the form designer window and double-click the Calculate Area button. Within the body of its subroutine, type "SquareArea()".
  3. Go back to the form designer window once more and double-click the Exit button. Within the body of its subroutine type "End".

The code in your code editor window should now look like this:


Your form's code should look like this

Your form's code should look like this


  1. Run and test the application to check that it works (you will need to type a value into the box labelled "Side" before clicking on the Calculate Perimeter or Calculate Area buttons). You should see something like the following:

Output from the Geometry program

Output from the Geometry program


Functions

Functions are procedures, just like subroutines. Unlike a subroutine, however, a function sends a value back to the routine that calls it (it is said to return a value). When creating a function, a slightly different syntax is used, as we shall see. To illustrate the use of functions, we need to modify our form somewhat, as follows (note that we have changed the size of the form to 180 × 340 pixels):


The amended frmGeometry form

The amended frmGeometry form


  1. Name the form controls as follows (some controls stay as they were):
Control
ControlName
FormfrmGeometry
TextBox 1txtLength
TextBox 2txtHeight
TextBox 3txtPerimeter
TextBox 4txtArea
Button 1cmdCalculate
Button 3cmdExit

We create a function by typing the Function keyword, followed by a procedure name (which should be chosen to reflect the purpose of the function), followed by parentheses. Because the function (unlike a subroutine) returns a value, you should specify the data type of the value the function will return by typing the As keyword to the right of the closing parenthesis, followed by the datatype of the value to be returned. To end the function, type the keywords End Function. The general syntax of a function declaration is shown below.

Function ProcedureName() As DataType
  .
  .
  .
End Function

The naming conventions used for fuctions are the same as those used for subroutines. As with subroutines, the code that is placed between the Function and End Function statements is called the body of the function, and defines what the function actually does. The variables required by the function - again, there can be any number of these - can be declared within the body of the function as follows:

Function GreetAlien() As String
  Dim strGreeting As String
  strGreeting = "Hello and welcome to Earth!"
  GreetAlien = strGreeting
End Function

The actions performed by the procedure will depend on the program statements that make up the body of the function. The above function simply declares a string variable called strGreeting and assigns it the value "Hello and welcome to Earth!". The function returns a value with the datatype String. The value actually returned by a function is determined by typing the name of the function, followed by an equals sign (=), followed by the name of the variable that holds the value to be returned. In the above example, the function returns the string value "Hello and welcome to Earth!".

Let's add some functions to our modGeometry module.

  1. Open the module's code editor window, delete the existing subroutines, and enter the following code:

Function RectPerimeter() As Double
  Dim dblLength As Double
  Dim dblHeight As Double
  dblLength = CDbl(frmGeometry.txtLength.Text)
  dblHeight = CDbl(frmGeometry.txtHeight.Text)
  RectPerimeter = (dblLength + dblHeight) * 2
End Function

Function RectArea() As Double
  Dim dblLength As Double
  Dim dblHeight As Double
  dblLength = CDbl(frmGeometry.txtLength.Text)
  dblHeight = CDbl(frmGeometry.txtHeight.Text)
  RectArea = dblLength * dblHeight
End Function

A function is called in much the same way as a subroutine, by typing its name in the section of code where it is to be used. The main difference is that the value returned by the function is assigned to a variable. In the following example, the function GreetAlien() is called when the form loads:

Private Sub Form_Load()
  Dim strCaption As String
  strCaption = GreetAlien()
End Sub

The primary purpose of a function is to return a value. In the above example, the return value of the GreetAlien() function is assigned to the variable strCaption when the form loads.

Let's call the functions we have added to the modGeometry module:

  1. In the frmGeometry class definition, delete the click event handlers for the cmdPerimeter button and the cmdArea button.
  2. Open the form designer window and double-click the Calculate button. The code editor window for the form will open and you will see that the following code has been created:

Private Sub cmdCalculate_Click(sender As Object, e As EventArgs) _
    Handles cmdCalculate.Click

End Sub

  1. In the body of this procedure, enter the code shown below:

txtPerimeter.Text = RectPerimeter()
txtArea.Text = RectArea()

  1. Run and test the application to check that it works (you will need to type values into the Length and Height boxes before clicking on the Calculate button). You should see something like the following:

Output from the new version of the "Geometry" program

Output from the new version of the Geometry program


Arguments and parameters

In the subroutines and functions examined above, calculations are made using the values entered into various text boxes on our form by the user. Although these procedures work perfectly well, they are explicitly coded to work with the values of these particular external variables, and therefore cannot be used in any other context. In order to make the procedures more generic - and thus reusable - we need to rewrite them to accept values from different sources. External values can be passed in to a procedure for processing, rather than being hard-wired into the procedure itself. The external values accepted by a procedure for processing are called arguments.

In Visual Basic, a list of the arguments that will be accepted by a procedure will appear within parentheses following the procedure name in a Sub . . . or Function . . . statement. The general syntax used for a subroutine that takes one or more arguments is as follows:

Sub ProcedureName(Argument[,Argument . . .])
  .
  .
  .
End Sub

And here is the construction used for a function:

Function ProcedureName(Argument[,Argument . . .]) As DataType
  .
  .
  .
End Function

When we write a procedure that can accept arguments, we need to tell the procedure how many arguments it will receive and specify the datatype for each argument. We also need to name each argument so that the code within our procedure can differentiate between the arguments passed to the procedure. We achieve this by declaring the name and datatype of each argument within the parentheses that follow the Sub ProcedureName or Function ProcedureName statement as a comma-separated list.

It is worth pausing here to clarify what we mean by the terms "argument" and "parameter" - since both terms are used in the heading for this section, and so far we have not mentioned parameters at all! Although the two terms are often used interchangeably, they do in fact have subtly different meanings.

The term argument refers to the actual value passed to a procedure. The term parameter (or formal parameter) refers to the declaration of the argument's name and datatype within parentheses in the Sub . . . or Function . . . statement that begins the procedure declaration. The list of arguments within parentheses is called a formal parameter list.

A procedure can accept any number of arguments, and each argument can be of a different type. The example below declares a subroutine called CalculatePrice that takes a double-precision floating point variable called dblItemPrice and an integer variable called intQty as its arguments.

Sub CalculatePrice(dblItemPrice As Double, intQty as Integer)
  Double dblPrice = intQty * dblItemPrice
  .
  .
  .
End Sub

We could also write a function that takes the same arguments as the subroutine, but assigns the result of the calculation to its return value (a double-precision floating point value):

Function CalculatePrice(dblItemPrice As Double, intQty as Integer) As Double
  Double dblPrice = intQty * dblItemPrice
  CalculatePrice = dblPrice
End Function

Procedures can use the arguments passed to them in exactly the same way they use locally declared variables. To demonstrate the use of arguments in procedures, modify the RectPerimeter() and RectArea() functions in your code module as follows:

Function RectPerimeter(dblLength As Double, dblHeight As Double)
  RectPerimeter = (dblLength + dblHeight) * 2
End Function

Function RectArea(dblLength As Double, dblHeight As Double)
  RectArea = dblLength * dblHeight
End Function

To call a procedure that takes one or more arguments, we type its name, followed by comma separated list of arguments within parentheses. The number of arguments should match the number of parameters specified in the procedure's formal parameter list. The arguments must be provided in the same order in which they appear in formal parameter list, and have the specified datatype.

In order for our application to work, we need to revise the code for the Calculate button as follows:

Private Sub cmdCalculate_Click(sender As Object, e As EventArgs) _
    Handles cmdCalculate.Click
  Dim dblInputLength, dblInputHeight As Double
  dblInputLength = CDbl(txtLength.Text)
  dblInputHeight = CDbl(txtHeight.Text)
  txtPerimeter.Text = RectPerimeter(dblInputLength, dblInputHeight)
  txtArea.Text = RectArea(dblInputLength, dblInputHeight)
End Sub

Once you have made the necessary changes, run the program again to test it.

Passing by reference

Each argument passed to a procedure usually consists of the current value of some variable. We call this passing by value, and it effectively makes a copy of the variable available to the procedure - the value of the original variable is not changed by anything the procedure does. Sometimes, however, we actually want a procedure to change the value of a variable. We can achieve this through a technique called passing by reference.

When we pass by reference, the argument passed to a procedure is the address in memory of a variable rather than its value. Any change the procedure makes to the value of the argument is applied to the original variable, not a copy. In order to pass a variable to a procedure by reference, we use the ByRef keyword before the name of the argument within the parentheses of the function declaration. To demonstrate this technique, make the following changes to the RectArea() function:

Public Function RectArea _
    (dblLength As Double, dblHeight As Double, ByRef dblArea As Double)
  RectArea = dblLength * dblHeight
  dblArea = RectArea
End Function

Now make the following changes to the code for the Calculate button:

Private Sub cmdCalculate_Click(sender As Object, e As EventArgs) _
    Handles cmdCalculate.Click
  Dim dblInputLength, dblInputHeight, dblArea As Double
  dblArea = 0
  dblInputLength = CDbl(txtLength.Text)
  dblInputHeight = CDbl(txtHeight.Text)
  txtPerimeter.Text = RectPerimeter(dblInputLength, dblInputHeight)
  RectArea(dblInputLength, dblInputHeight, dblArea)
  txtArea.Text = dblArea
End Sub

Note that the changes we have made mean that the value of dblArea is passed to the RectArea() function by reference, which means that the value of dblArea will be changed by this function call. Note also that by default, even though we do not use the ByVal keyword in front of the formal parameters dblLength As Double and dblHeight As Double for the RectArea() function, these variables will be passed to the function by value. Variables are only passed to a function by reference if the ByRef keyword is used.