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.
- Create a new project called "Modules"
- Change the Text property of the main form to "Geometry"
- Change the name of the form to "frmGeometry"
- Set the application's Startup Object property to Modules.frmGeometry.
- Add controls to the form as shown below (we have used a form size of 300 x 240)
Your form should look like this screenshot
- Change the names of the controls as specified in the table below
Control | Name |
---|---|
Form | frmGeometry |
TextBox 1 | txtSide |
TextBox 2 | txtPerimeter |
TextBox 3 | txtArea |
Button 1 | cmdPerimeter |
Button 2 | cmdArea |
Button 3 | cmdExit |
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:
- Click on Project ► Add Module . . . . You will see the following dialogue box:
The Add New Item ► Modules dialogue box
- 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.
- 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:
- 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
- In the body of the subroutine, type "SquarePerimeter()"
- Now go back to the form designer window and double-click the Calculate Area button. Within the body of its subroutine, type "SquareArea()".
- 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
- 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
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
- Name the form controls as follows (some controls stay as they were):
Control | Name |
---|---|
Form | frmGeometry |
TextBox 1 | txtLength |
TextBox 2 | txtHeight |
TextBox 3 | txtPerimeter |
TextBox 4 | txtArea |
Button 1 | cmdCalculate |
Button 3 | cmdExit |
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.
- 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:
- In the frmGeometry class definition, delete the click event handlers for the cmdPerimeter button and the cmdArea button.
- 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
- In the body of this procedure, enter the code shown below:
txtPerimeter.Text = RectPerimeter()
txtArea.Text = RectArea()
- 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
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.