Subroutines and User-Defined Functions

As your macros become more complicated, you can split the code into self-contained units called subroutines and user-defined functions. A subroutine carries out a task; it is like a custom-made statement. A user-defined function returns information and may also carry out a task. Subroutines and functions offer at least two advantages:

Subroutines

A subroutine is a kind of macro within a macro. It carries out a particular task, just as the macro as a whole does. Virtually any part of a macro can be placed in a subroutine, but usually it only makes sense to create a subroutine for a part that is self-contained or that will be used more than once. For this reason, subroutines are generally not needed in simple macros.

Every macro has a main subroutine that begins with the instruction Sub MAIN. A subroutine that you create is defined in the same way as the main subroutine, but requires a different name:

Sub SubroutineName[(ArgumentList)]
Series of instructions
End Sub

The SubroutineName is whatever name you choose that isn't a reserved word or a variable name. The limitations are the same as those for variable names (see "Variables" in Chapter 3, "WordBasic Fundamentals"). ArgumentList is a list of variables, separated by commas, that accept values passed to the subroutine. See "Sharing Values Among Subroutines and Functions" later in this chapter.

The Sub…End Sub instructions cannot be placed within the main subroutine or within any other subroutine. In other words, you cannot nest subroutines as you can WordBasic control structures, such as For…Next loops or If conditionals.

On the other hand, the instruction that runs, or "calls," the subroutine is placed within another subroutine. You can use the Call statement to run a subroutine, or you can use just the name of the subroutine as the instruction. (The Call keyword makes the macro easier to read — it's clear that a subroutine and not a built-in WordBasic statement is being run.) After the instructions in the subroutine have been carried out, control reverts to the routine that called the subroutine, as shown in the following diagram.

In the following example, the BeepMsgExit subroutine is called if the user chooses the Yes button in a message box that asks if he or she wants to quit Word:


Sub MAIN
    response = MsgBox("Do you want to quit Word?", 4)
    If response = -1 Then
        Call BeepMsgExit
    End If
End Sub

Sub BeepMsgExit
    Beep
    MsgBox("Quitting Word now...", -8) 
    FileExit 1
End Sub

User-Defined Functions

User-defined functions are similar to subroutines — they are self-contained units of code that are called from one or more subroutines. But unlike a subroutine, a user-defined function returns a value, just as built-in WordBasic functions do. The rules for using functions you create are the same as those for built-in functions. Generally, you define a function because WordBasic does not include a function designed to return the particular value you need.

You define new functions in a manner similar to subroutines, except that instead of using the Sub instruction, you use the Function instruction. The syntax is as follows:

Function FunctionName[(ArgumentList)]
Series of instructions
FunctionName = value
End Function

The ArgumentList is a list of variables, separated by commas, that accept values passed to the function. The function returns a value by assigning it to the function name itself, using the syntax FunctionName = value. A user-defined function can return either a numeric or string value. Functions that return string values have function names that end with the dollar sign ($).

In the following example, the MyDateTime$ function is called by the main subroutine to display the date and time in a message box. Note that no arguments are passed to this function, so it does not end with parentheses as a built-in Word function would (for example, Font$()); it looks just like a variable.


Sub MAIN
    MsgBox "The date and time is: " + MyDateTime$
End Sub

Function MyDateTime$
    mDate$ = Date$()
    mTime$ = Time$()  
    MyDateTime$ = mDate$ + " " + mTime$
End Function

Sharing Values Among Subroutines and Functions

By default, a variable is available only within the subroutine or function in which it is initially used. However, subroutines and functions often need to exchange or share values. The following two methods are available:

Shared Variables

A shared variable can be used in any subroutine or function in the macro. Here is the syntax for declaring a shared variable:

Dim Shared Var, Var1, Var2

A single Dim Shared instruction can be used to declare several shared variables. Every type of variable, including number, string, and array variables, can be declared as shared. A Dim Shared instruction is not placed inside a subroutine or function since it applies to all the subroutines and functions within the macro. Generally, you declare shared variables at the beginning of a macro, before the main subroutine.

In the following example, the variable num is declared as shared:


Dim Shared num                    'Declare "num" as a shared variable
Sub MAIN
    num = 6                        'Set the value of num
    AddTenRoutine                'Call the routine
End Sub

Sub AddTenRoutine
    num = num + 10                'Increase the value of num by 10
    Print num                    'Display the new value of num
End Sub

The main subroutine sets num equal to 6, and then calls the AddTenRoutine subroutine. The subroutine adds 10 to num and then displays the value 16. If num was not declared as a shared variable, the value displayed would be 10, because num would have no value at the start of the AddTenRoutine subroutine.

Any subroutine can affect the value of a shared variable. If you have many subroutines, shared variables can be a source of problems. For example, you might intend to use a variable named "count" in two subroutines. Later on, if you forget that you've already used "count" and you use it in a new subroutine, you could create an error that might be difficult to find. You can avoid this problem by passing variable values through subroutine or function arguments.

Passing Values to Subroutines and Functions

You can pass values from one subroutine or user-defined function directly to another through arguments. Arguments are variables in a subroutine or function that initially receive their values from the calling subroutine or function. Unlike shared variables, whose values can be affected by any subroutine or function, values passed to subroutines and user-defined functions can be affected only by the subroutines or functions involved.

Here is the syntax for subroutine arguments in the Call statement:

[Call] SubroutineName [argument1, argument2, …]

The syntax for passing values to a user-defined function is similar; the main difference is that the arguments are enclosed in parentheses:

FunctionName[(argument1, argument2,)]

Note that you can pass any number of values, each separated by a comma.

Here is an example of passing a value to the AddTenRoutine shown earlier:


Sub MAIN
    num = 6                    'Set the value of num
    AddTenRoutine num        'Call the routine and specify "num"
End Sub

Sub AddTenRoutine(var1)
    var1 = var1 + 10            'Increase the value of var1 by 10
    Print var1                'Display the new value of var1
End Sub

The following example uses a function called FindAverage() to average two numbers. The variables a and b, defined as 6 and 10, are passed to the FindAverage() function, which averages the two values. The main subroutine then displays the result.


Sub MAIN
    a = 6
    b = 10
    Print FindAverage(a, b)
End Sub

Function FindAverage(firstnum, secondnum)
    FindAverage = (firstnum + secondnum) / 2
End Function

Note that the variable names in the subroutine or user-defined function do not have to match the names of the variables passed to it, but the order of the arguments must match. In the previous example, the value of the a variable is passed to the firstnum variable, and the value of b is passed to secondnum.

You can pass strings, numbers, and arrays to subroutines and user-defined functions. In the following example, the fontnames$() array is passed to the FillFontArray subroutine, which fills the array with the list of available font names.


Sub MAIN
    lastelement = CountFonts() - 1
    Dim fontnames$(lastelement)
    FillFontArray$(fontnames$(), lastelement)
End Sub

Sub FillFontArray(array$(), maxcount)
    For arraycount = 0 To maxcount
        array$(arraycount) = Font$(arraycount + 1)
    Next
End Sub

Passing Arguments "by Value"

Normally, when you pass a variable to a subroutine or user-defined function, the subroutine or function can change the value of that variable not only within the subroutine or function itself but also in the calling subroutine. This is known as passing an argument "by reference." In the following example, the greeting$ variable is passed to the ChangeGreeting subroutine by reference. The main subroutine then displays the greeting, which the ChangeGreeting subroutine has changed from "Hello" to "What's up?"


Sub MAIN
    greeting$ = "Hello"
    ChangeGreeting greeting$
    MsgBox greeting$
End Sub

Sub ChangeGreeting(change$)
    change$ = "What's up?"
End Sub

You can pass an argument and ensure that its value in the calling subroutine remains unchanged by passing the argument "by value." To pass an argument by value in WordBasic, you enclose it in parentheses.

In the following example, the greeting$ argument is passed by value, so when the main subroutine displays the greeting, the greeting remains "Hello."


Sub MAIN
    greeting$ = "Hello"
    ChangeGreeting (greeting$)
    MsgBox greeting$
End Sub

Sub ChangeGreeting(change$)
    change$ = "What's up?"
End Sub

In the following example, the variable a is passed by reference, while b is passed by value:


Sub MAIN
    a = 6
    b = 10
    OnePlusAverage a,(b)
    MsgBox "a =" + Str$(a) + " and b =" + Str$(b)
End Sub

Sub OnePlusAverage(firstval, secondval)
    firstval = firstval + 1
    secondval = secondval + 1
    avg =  (firstval + secondval) / 2
    Print avg
End Sub

The OnePlusAverage subroutine adds 1 to each value passed to it, and then displays the average of the two values in the status bar. When the OnePlusAverage subroutine ends and control returns to the main subroutine, the main subroutine displays the following message box, which shows that the argument passed "by reference" changed, while the argument passed "by value" did not.

Note that if you are passing more than one argument to a subroutine and you want to pass the first argument by value, you must enclose the list of arguments in parentheses. For example, if you wanted to pass a by value and b by reference, you would specify the following instruction:


OnePlusAverage((a),b)

This issue doesn't arise when you're passing arguments to a user-defined function, since the list of arguments passed to a function must always be enclosed in parentheses.

Using Subroutines and Functions from Other Macros

You can call subroutines and functions that are stored in other macros. This technique lets you create libraries of subroutines and functions so that you can avoid copying or rewriting code you use often.

To call a subroutine stored in another macro, use the following syntax:

[Call] MacroName.SubroutineName[(ArgumentList)]

MacroName is the name of the macro containing the subroutine, and SubroutineName is the name of the subroutine you want to use. The optional ArgumentList is the list of values to be passed to the subroutine in the same way values are passed within the same macro. You can pass string and numeric values and arrays, just as you can within a macro.

The template in which the specified macro is stored can be the active template, the Normal template, or a loaded global template. Subroutines and functions stored in the Normal template are always available. For information on loading a template as a global template, see "Macros and Templates" in Chapter 2, "Getting Started with Macros."

The following example is a subroutine contained in a macro called Library:


Sub MyBeep
    Beep : Beep : Beep                        'Beep three times
    For t = 1 to 100 : Next                    'Pause
    Beep : Beep : Beep                        'Beep three times again
End Sub

Here's an example of a macro that calls the MyBeep subroutine:


Sub MAIN
    YES = -1
    answer = Msgbox("Listen to MyBeep?", 36)    'Prompt user
    If answer = YES Then Library.MyBeep        'If yes, run MyBeep
End Sub

To use a function stored in another macro, use the following syntax:
MacroName.FunctionName [(ArgumentList)]. For example, suppose the following function is stored in the Library macro:


Function MyDateTime$
    mDate$ = Date$()
    mTime$ = Time$() 
    MyDateTime$ = mDate$ + " " + mTime$
End Function

In a macro called CheckDateTime, you could call this function as follows:


Sub MAIN
    MsgBox "The date and time is: " + Library.MyDateTime$
End Sub

Running Another Macro from Within Your Macro

In addition to being able to call subroutines and functions stored in other macros, you can run entire macros from within your macro. It can be useful to call a macro for a specific task from within a larger macro that performs several tasks. You can call a macro in the same way you call a subroutine or function, with or without the optional Call keyword. For example, to call a macro named ChangeSettings, you can use the following instruction:

Call ChangeSettings

Or you can omit the Call keyword:

ChangeSettings

You can also use ToolsMacro to run a macro:

ToolsMacro .Name = "ChangeSettings", .Show = 3, .Run

One advantage of using ToolsMacro to run a macro is that you can specify the template where the macro you want to run is stored. In the previous example, ToolsMacro will run the ChangeSettings macro stored in the template attached to the active document. When you use the Call statement, Word looks for the called macro first in the template that contains the calling macro. Only if the macro is not found there does Word look in other templates. This distinction between ToolsMacro and Call can make a difference if different available templates contain macros with the same names.


Here are some points to be aware of when calling subroutines or functions from another macro:

Important

You cannot call a subroutine or user-defined function stored in another macro if the name of the subroutine or function is the same as the name of an argument for a WordBasic statement that corresponds to a dialog box. For example, Library.Wrap generates an error because .Wrap is an argument for the EditFind statement.