You might not be aware of it, but you've been working with subs and functions for a while. Event procedures such as Click() and Load() are subs, and Visual Basic comes with many predefined functions built right into it, such as LoadPicture() and Len().
Visual Basic is a procedural language--that is, you can make blocks of code that can be referred to by a name. After a block of code has a name, it can be called and executed. In other words, you can write some lines of code, enclose them in a code block, give the block a name, and then call the block when you need it. It's almost like having a program within a program. These little programs that live within larger programs are called "functions" if they return a value and "subs" if they don't.
Programmers have written user-defined subs and functions for years. (In fact, the term "sub" is a shortened form of "subroutine" that gradually became its own word.) They make coding easier, faster, and more robust. Also, making your own subs and functions puts you on the road to writing encapsulated and reusable code. Encapsulation is simply the methods and properties of an object enclosed behind a public interface.
Making code changes easilySubs enable you to change code easily. If you have a body of code that you need to use repeatedly, put the code in a sub. Then, if you need to make a change in the code, simply go to the sub to make changes. If you don't put the code in a sub, you will have to go to every instance of the code in your program to make the required change. The more dispersed your code is, the harder it is to make changes effectively and efficiently.
A sub is a procedure that executes the lines of code within its block but doesn't return a value. The syntax for a simple sub is as follows:
[Private|Public] Sub SubName() .....lines of code End Sub
In this syntax
The following code snippet is an example of a simple sub:
Public Sub DataNotFound() MsgBox "Data Not Found", vbInformation End Sub
When you call this sub from other areas of your code, the sub displays a Windows message box with the string Data Not Found. The message box will display whatever text is used for the string constant.
Listing 18.1 shows the sub being called with the Visual Basic Call statement (line 2). Using the Call statement is optional. Although you can call a Sub by using only its name, using the Call keyword makes your code more readable.
01 Private Sub itmOpen_Click()
02 Call DataNotFound
03 End Sub
You can add a sub to your project in two ways:
Enabling the Add Procedure menu itemFor the Add Procedure menu item to be enabled, you must be in Code window view of the form or module into which you want to add the procedure.
Add a sub to your project with Add Procedure
FIGURE 18.1 The Add Procedure dialog lets you create subs and functions for all types of Visual Basic projects, as well as properties and events for ActiveX controls and ActiveX servers.
FIGURE 18.2 You'll find the new sub in the General section of the form or module.
After you create the sub code block with the Add Procedure dialog, you add the procedure's code within the code block. Don't enter any code for the Sub after the End Sub keywords; this is illegal and generates syntax errors when you compile the code.
A function is a procedure that executes lines of code and returns a value. The syntax for declaring a simple function is as follows:
[Private|Public] Function FunctionName() As DataType ...lines of code FunctionName = ReturnValue End Function
In this syntax
The code snippet in Listing 18.2 shows a function, GetNumber(), the purpose of which is to return a number defined within the function itself.
01 Public Function GetNumber() As Integer
02 Dim a%
03 Dim b%
04 Dim c%
05 `Assign values to some variables
06 a% = 7
07 b% = 12
08
09 `Add them together
10 c% = a% + b%
11
12 `Pass the result out of the function by assigning
13 `it to the function name.
14 GetNumber = c%
15 End Function
You add a function to your project by using the same two methods that you use to add a sub--by putting it directly into the General Declarations section of the form or module or by using the Add Procedure dialog. However, be advised that you have to manually add a little code when you add a function to your code by using the Add Procedure dialog (see Figure 18.3).
FIGURE 18.3 Add the code block in the Add Procedure dialog.
You can enhance the power and versatility of subs and functions by using arguments. An argument, also referred to as a parameter, is a variable that acts as a placeholder for a value that you'll pass into the sub or function. You create arguments by placing them within the parentheses of the declaration statement of the sub or function. The following snippet of code shows the declaration for the function EndDay(), which takes two arguments: one of type Integer and one of type String.
EndDay(NumOne As Integer, strName As String) As Integer
Using arguments greatly increases the reusability of your code. For example, imagine that in many places of your code you need to figure out the greater of two numbers. Every time you need to do this calculation, you could write out the code, line for line, or you could write out a function that does this for you and then call the function when you need to do the calculation. The advantage of the latter method is twofold:
Listing 18.3 shows the user-defined function GetGreaterNum(), which returns the greater of two numbers passed to it.
01 Public Function GetGreaterNum(NumOne As Integer, _
NumTwo As Integer) As Integer
02 `If the first number is greater than the second
03 If NumOne > NumTwo Then
04 `return the first number
05 GetGreaterNum = NumOne
06 Else
07 `if not, return the second number
08 GetGreaterNum = NumTwo
09 End If
10 End Function
Listing 18.4 shows the GetGreaterNum() function called from within a Click() event procedure.
0 1 Private Sub cmdGreaterNum_Click()
02 Dim i%
03 Dim j%
04 Dim RetVal%
05
06 `Get the input in txtNumOne and convert it to an integer
07 i% = CInt(txtNumOne.Text)
08
09 `Get the input in txtNumTwo and convert it to an integer
10 j% = CInt(txtNumTwo.Text)
11
12 RetVal% = GetGreaterNum(i%, j%)
13
14 `Take the result from the function, convert it to a
15 `string and assign it to the caption of the button.
16 cmdGreaterNum.Caption = CStr(RetVal%)
17 End Sub
It's very important when you use subs or functions that the argument's type and order match up. If you have a procedure that has three arguments of type Integer, you must pass in three integers; if you pass in two Integers and a String, the compiler will throw an error. For example, if you have a function EndDay() declared as follows,
Public Function EndDay(iNum As Integer, dAccount _ As Double) As Double
and call the function by using the following line of code,
dMyResult = EndDay(6, "D56R")
this call generates an error. "D56R" is of type String, but the function is expecting the second argument to be of type Double. For this reason, a variable type declaration error appears.
Also, the argument count must match up. For example, you have a function declared as follows:
Public Function Bar(iNum as Integer, dNum as Double, _ strName as String) as Integer
and you call the function by using the following line of code:
iMyResult = Bar(6, 7)
This call also causes an error. The function expects three arguments, but you've passed in only two. Again, an error occurs.
It's possible to make an argument optional by using the Optional keyword before an argument when you declare the function. If there's an upper limit on the number of arguments that you're going to pass, you should use the Optional keyword. These arguments must be declared as Variant.
Avoid problems with named argumentsUsing named arguments enables you to avoid problems that might arise in function use because of the ordering of arguments.
You can use named arguments to make passing arguments to a procedure easier. A named argument is the literal name of an argument in a procedure. For example, if you have a function EndDay() that takes two arguments, NumOne and NumTwo, of type Integer, you define it as follows:
To pass a value to the function by using named arguments, you use the names of the arguments and assign values to them by using the := characters. Thus, to pass actual values into EndDay() by using named arguments, you do the following:
X = EndDay(NumOne:=3, NumTwo:=4)
Sometimes you need to leave a procedure before it finishes. You do this by using the Exit keyword. Listing 18.5 shows the function ExitEarly(), which takes two arguments: an Integer used to determine the upper limit of a loop and an Integer that flags the function when a special condition exists that requires the function to be exited early.
01 Public Function ExitEarly(iLimit As Integer, _
iFlag As Integer) As Integer
02 Dim i%
03 Dim Limit%
04 Dim Flag%
05
06 `Assign the limit argument to a local variable
07 Limit% = iLimit
08
09 `Assign the state argument to local variable
10 Flag%= iFlag
11
12 `Run a For...Next loop to Limit%
13 For i% = 0 To Limit%
14
15 `If the passed in state is one
16 If Flag% = 1 Then
17
18 `Check to see if i% equals half the value of
19 `the Limit variable
20 If i% = Limit% / 2 Then
21
22 `If it does, pass out the value of i%
23 `at that point
24 ExitEarly = i%
25
26 `Terminate the function; there is no
27 `reason to go on
28 Exit Function
29 End If
30 End If
31 Next i%
32
33 `If you made it this far, the flag variable does not
34 `equal one, so pass the value of i% out of the
35 `function by assigning the value of i% to the
36 `function name.
37 ExitEarly = i%
38
39 End Function
The ExitEarly() function works by taking the iLimit argument, assigning it to a variable local to the function (line 7) and then using that local variable to be the upper limit of a For...Next loop (line 13). The function also takes the iFlag argument and assigns that variable to one that's also local to the function (line 10). Then the For...Next loop is run. Within the loop, if the value of the local Flag variable is 1 (line 16), an If...Then statement checks the value of the counting variable, i%, to see whether it's equal to half the value of the variable Limit (line 20). If it is, the value is assigned to the function's name (line 24) to be passed back to the call, and the Exit keyword terminates the execution of the function (line 28). If the value of the local variable Flag is other than 1, the loop continues until it reaches its limit (line 31). Then the value of i% is assigned to the function's name, and control is returned to the calling code (line 36).
When you create a user-defined function, the Visual Basic IDE treats it as though it were an intrinsic function. This means that the function is listed in the Object Browser and appears in the Quick Info window (see Figure 18.4).
FIGURE 18.4 One nice feature of Visual Basic 6 is that the Quick Info window pops up even for user-defined subs and functions.
Scope is the capability of two different variables to have the same name and maintain different values and lifetimes. Listing 18.6 shows two functions, EndDay() and Bar().
01 Public Function EndDay() as Integer
02 Dim x as Integer
03 Dim y as Integer
04
05 x = 2
06 y = 7
07 EndDay = x + y
08 End Function
09
10 Public Function Bar() as Integer
11 Dim x as Integer
12 Dim y as Integer
13
14 x = 12
15 y = 34
16 Bar = x * y
17 End Function
Notice that each function declares variables x and y. Also notice that those variables are assigned different values within each function (lines 5-6 and 14-15). This is possible because each set of variables exists only where it's created. In the function EndDay(), the variables x and y are created in lines 2 and 3. When the function ends in line 8, the variables are removed from memory and no longer exist. (This is known as going out of scope.) The same is true of the x and y variables in the Bar() function. Neither variable set can see the other. If you wanted to create variables i and j, the values of which could be seen by both functions, you would create the variables higher in scope in the General Declarations section of a form or module, using the Public or Private keywords when declaring them.
Although the EarlyExit()function is functionally adequate, it's difficult to implement from project to project. If other programmers wanted to use it, they would have to take more than a passing glance to figure out what the function is about and how to put it to good use. Proper documentation addresses this deficiency.
All subs and functions should have a header, a section of commented code that appears at the top of a code block. The header usually gives a synopsis of the procedure: the procedure name, a description of the arguments and return value if any, and some remarks as to what the procedure is about, with any special instructions. Also in the header is a history of when and who created the code. If any changes are made to the code, a description and date of the changes are added to the header. Finally, the header contains the appropriate copyright information.
You should also comment each task within the procedure. This saves time for others who will maintain your code. Commenting your code will save you a lot of effort when it comes time to revisit the code later. Listing 18.7 shows the ExitEarly() function commented in a professional manner (line numbering has been omitted for the sake of clarity).
01 Public Function ExitEarly(iLimit As Integer, _
02 iFlag As Integer) As Integer
03 `****************************************
04 `Sub/Function: ExitEarly
05 `
06 `Arguments: iLimit The upper limit of the For..Next Loop
07 ` iFlag An integer indicating early exit from
08 ` the function. 1 = Exit.
09 ` Other values are ignored.
10 `
11 `Return: The value of the For...Next loop counter
12 `
13 `Remarks: This function is used to demonstrate the way
14 ` to use arguments within a function
15 `
16 `Programmer: Bob Reselman
17 `
18 `History: Created 4/20/98
19 `
20 `Copyright 1998, Macmillan Publishing
21 `****************************************
22
23 Dim i% `Counter variable
24 Dim Limit% `Internal variable for the upper limit of the
25 `For...Next loop
26 Dim Flag% `Internal variable for the exit flag
27
28 `Assign the limit argument to a local variable
29 Limit% = iLimit
30
31 `Assign the state argument to local variable
32 Flag% = iFlag
33
34 `Run a For...Next loop to Limit%
35 For i% = 0 To Limit%
36
37 `If the passed in state is one
38 If Flag% = 1 Then
39
40 `Check to see if i% equals half the value of
41 `the Limit variable
42 If i% = Limit% / 2 Then
43
44 `If it does, pass out the value of i%
45 `at that point
46 ExitEarly = i%
47
48 `Terminate the function; there is no reason
49 `to go on
50 Exit Function
51 End If
52 End If
53 Next i%
54
55 `If you made it this far, the state variable does not
56 `equal one, so pass the value of i% out of the function
57 `by assigning the value of i% to the function name.
58 ExitEarly = i%
59
60 End Function
By default, when you start a Visual Basic project, the first form created will be the first form that the project loads. This is adequate if you have a one-form project. What if you have a project with many forms or a project with no forms at all? For the multiple-form problem, you could "chain" Form_Load() event procedures--have one form's Load event procedure Load another form--as follows:
Private Sub Form_Load() Load frmAnotherForm End Sub
This would work adequately for projects with a limited amount of forms, but it's not the best programming practice, particularly if you have projects that require the presentation of many forms.
For projects that have no forms (and there are such projects, particularly in server-side Internet programs that use Visual Basic), there's nothing to load. What do you do for an entry point (or starting point) for your program? Visual Basic provides a nonform-based entry point for your program--the Sub Main() procedure. Sub Main() is a special procedure reserved by Visual Basic as the startup procedure for any given project. Sub Main() must be declared in a module, and there can be only one Sub Main() per project.
Set Sub Main() to be the startup point for a project
FIGURE 18.5 You can choose Sub Main() or any form in your project as the startup object.
After you define Sub Main() to be the startup object for your project, you need to create Sub Main in a module. You can use the Add Procedure dialog that you've used to create user-defined procedures, or you can manually enter the declaration in the General section of your chosen module. Remember, a project can have only one Sub Main(). After you create Sub Main(), you need to fill in some startup code.
Listing 18.8 shows a Sub Main() that displays two forms by using the Show method (lines 4 and 5) and then displays a message box after all the forms are visible (line 8).
01 Sub Main()
02 `Use the Show method to display both
03 `forms upon startup
04 frmMain.Show
05 frmOther.Show
06
07 `Report that all forms are shown
08 MsgBox "Everything shown"
09 End Sub
The code for some versions of Sub Main() can be simple, but some Sub Main() procedures can be complex. Listing 18.9 shows how Sub Main() is used to invoke a comprehensive startup routine that calls other procedures. This listing is the Sub Main() procedure for the VBScheduler program that you can download from http://www.mcp.com/info.
01 Sub Main()
02 `Load the form and let it run the code in the
03 `Form_Load event handler
04 Load frmMain
05 `Intialize the contact list combo box
06 Call InitComboAsDb(frmMain.cboName, frmMain.DataMain)
07 `Fill the appointment list with today's appoinments
08 Call GetDailyAppointments(CDbl(Cdate _
(frmMain.FormDateString())), _
frmMain.lstSchedule, _
frmMain.DataMain, _
gAppointmentDelta%)
09 `Show the main form
10 frmMain.Show
11 `Set the mouse pointer back to an arrow
12 frmMain.MousePointer = 0
13 End Sub
© Copyright, Macmillan Computer Publishing. All rights reserved.