|By: Paul S. Cilwa||Viewed: 1/21/2020
||Topics/Keywords: #VisualBasic6.0 #ProgrammingforMicrosoftWindows||Page Views: 2442|
|Chapter 3 of this free online course in Visual Basic 6.0.|
If you have been programming in the "linear" (non-object-oriented) fashion for years, you may wonder—perhaps with some trepidation—how big a change object-oriented programming will bring. To ease your concerns, let's look at the linear code modules that can contribute to some Visual Basic programs. Also remember that, each of your object-oriented components will be built up of linear snippets! So, on a line-by-line basis, object-oriented programming isn't so different from linear programming, after all.
As mentioned in the previous chapter, each file that makes up your project is called a module. There are many different kinds of module: form modules, class modules, property pages, user controls, and just plain code modules. These last are composed of linear code and variable declarations, and we'll look at them in this chapter.
Microsoft calls them just "modules", but code module is a better term, since it distinguishes from the other types of module. Still, remember when you are inserting one into your project, that they are called just plain "module" on the menu!
What, in an object-oriented program, properly belongs in a code module? The following types of items are considered appropriate:
- Global variables and procedures not "attached" to any particular object
- Programmer-defined data definitions
- Procedure declarations for external APIs, such as built-in Windows functions
Please note that global variables are not encouraged in any object-oriented application. Still, sometimes you need one!
Adding a Code Module to Your Project
Use the Project..Add Module command to add a code module to your project.
The command causes the Add Module dialog to appear. When you first install Visual Basic, only a basic module is available to choose from. However, you can add pre-written, stock modules of your own to the list; choosing one will make a copy and add it to your project.
Choose Project..Add Module from the menu. When the dialog appears, click the OK button. You now have a module! We'll put some code in it shortly.
Once you have added a module to your project—any kind of module—you will want to give it a name. Although the names of code modules aren't actually used much in a project. the names of other modules are used extensively, since the name of the module is also the name of the object class the module describes.
To name a module, when the module's code window is open and active, go to the properties window (click F4 if it isn't visible) and simply type the name you prefer, in place of the one Visual Basic gave it by default. For example, the module we just added is named "Module1".
Change the name of Module1 to the more descriptive-of-purpose Globals.
The Code Window
The code window has a number of features. Since you'll be spending a lot of time working with code windows, it will be a good idea to become familiar with them.
The caption bar of a code window advises you of the project to which the code belongs, the name of the module and the fact it is a code window. (There are also designers, which are used to design forms and other visual objects.)
In a code module, there is only one "object", called "(General)". However, in other module types, there will be more.
Whatever object you select in the object list, the Procedure List will then contain a list of procedures that apply to that object. In a code module, all procedures belong to "(General)". Variables and Declare statements always go in the "(Declarations)" procedure, which isn't really a procedure, at all! "(General)" is the only "object" that ever has a "(Declarations)" section.
If you need to see two sections of code in the same module, drag the splitter bar downwards. You can then click in either "pane", and select a different procedure by choosing entries from the Object and Procedure lists. This is usually a better way of looking at different sections of a module than Full Module View.
Clicking on this button allows you to focus on just one procedure (or two, if you are using the Splitter Bar) at a time. Although you may be used to working with entire code modules in your previous language, I suggest you give Procedure View a try. That level of focus is very supportive of your object-oriented programming.
Full Module View
There are times when you need to work with the module as a whole—for example, when copying blocks of procedures. When the need arises, clicking this button gives you Full Module View.
The bulk of the window is there for you to write code. As we'll see, shortly, the code editor provides many ways of assisting you so you don't have to actually type every character!
So, what do you put in a code window?
As in most languages, the data with which you work in Visual Basic takes the form of literals, constants and variables.
A literal is a string of characters delimited by quotes, or an integral or floating point number (in base 10 or in hexadecimal), or the keywords True or False. Here are some examples of literals:
- "But I feared what my eyes might spy next."
- "This string includes an embedded ""quote""."
We'll postpone our look at named constants and variables until we've had a chance to consider the concepts of lifetime and scope.
Lifetime and Scope
These two concepts are related, but distinct. Lifetime refers to how long a variable lives: When it comes into existence, and when it ceases to exist. Scope refers to where, in your application, a given variable can be accessed.
Local, Temporary Variables
This is the smallest scope and most ephemeral lifetime. Local, temporary variables are declared within a procedure; they come into existence when they are declared, and evaporate when the procedure is exited. They can also be directly accessed only within the procedure in which they are declared. They are declared with the Dim keyword, which puzzlingly stands for "dimension".
Remember those graduate math students who originally designed BASIC? They had previously taken courses in FORTRAN, and remembered well how hard it was for them to grasp the idea of declaring variables. On paper, of course, when a math student needs a variable for an equation, he or she simply writes one.
So, the grad students wanted to save the undergrads from this psychic torment, and designed BASIC so that it had pre-defined variables, as described earlier. However, there was one situation in which they could not predefine memory space, and that was in the area of matrix arithmetic. Such math requires dimensioned arrays, and the number of elements needed depends on the specific math problem being solved.
So, there was, even in the earliest BASIC, a statement called DIM whose purpose was to dimension (that is, allocate) an array.
Programmers fluent in BASIC began to refer to this as
"dimming an array." And others would sometimes lament the fact that BASIC wouldn't let them "dim a variable."
Years later, when BASIC was extended to allow the declaration of individual variables, someone—who probably didn't even know the origin of the name of the DIM statement—did, indeed, use that word to describe the allocating of a single variable. And, thus, today, we find the Dim statement in Visual Basic.
Example of a local, temporary, variable:
Public Sub MyProcedure ()
Dim A as Integer A = 77 End Sub
Local, Static Variables
These variables are local in scope, that is they are only directly accessible in the procedure in which they were declared. However, they outlive the procedure. They actually come into existence when the application is loaded into memory, and continue to exist until the application is terminated. They are declared with the Static keyword.
A local, static, variable intentionally breaks the context-free reentrancy of a procedure. In other words, it allows a procedure to "remember its past lives." Here's an example of a local, static, variable:
Public Sub MyProcedure () Static A as Integer A = A + 1 End Sub
Module Scope Variables
Module scope variables, which also have lifetimes equal to that of the entire application, can be accessed anywhere in the module in which they are declared—except for within procedure with a local variable of the same name. They are declared with the Private keyword, and must be declared in the (General) (Declarations) section of a [code] module.
Option Explicit Private A as Integer
Global variables, which can be accessed from anywhere in the project (except where they are hidden by module or local variables of the same name), must also be declared in the (General) (Declarations) section of a [code] module. The difference is that they are declared using the Public keyword.
In earlier versions of Visual Basic, globals were declared with the Global keyword. Although the compiler still recognizes this keyword, it is considered obsolete.
Although global and module-level variables can have the same name, you can't declare them both in the same module. Here's an example of a global declaration:
Option Explicit Public A as Integer
The variable lifetime and scope that we've just reviewed all apply to linear code—that is, declarations in [code] modules, not forms, classes, or other module types (although local variables are also used the same way in object-oriented code). Later, we'll look at the choices available for objects.
When you declare a variable, you generally have some idea of how you intend to use it—specifically, what type of data it will hold: A text string? A number? A date or time? If so, you can make your application run much more efficiently if you declare the variable to be of a specific type. Here are the choices:
This data type is used for small, unsigned (positive) whole numbers whose values lie between zero and 255. Variables of this type only take up a single byte (eight bits), hence the name. Of all the numeric data types, this is the only one that is unsigned.
Variables of the Integer type take up 16 bits (two bytes, also called a "word"), and this can contain integral values ranging from -32,768 to 32,767. This is the most commonly-used data type.
Add a global variable to the module you just created. It's name will be UnnamedDocumentCount, it will be an Integer, and its purpose will be to keep track of how many new documents an instance of our application has already created.
Make sure you have a code window opened to the (General) (Declarations) section of the Globals.bas [code] module, and add the following boldfaced code (the other line should already be there):
Option Explicit Public UnnamedDocumentCount as Integer
One of the things you'll notice, as you type, is that when you hit the space after the word "as", you'll be presented with a list of possibilities for completing the statement. As you type "I", then "n", and so on, the list will be narrowed down to just a few, or even the one, item. At any time that you become aware the selection is, indeed, the item you want, you can complete the typing of it by simply hitting the Tab key. Alternatively, you can use the arrow keys to select an item, or you can double-click it with the mouse.
This feature means you are no longer limited by your typing skills! In fact, you can now enter code far faster than someone who insists on typing it all out.
When Integers aren't big enough, consider using a Long. Occupying four bytes (32 bits, also known as a "paragraph"), these variables can represent integral values between -2,147,483,648 and 2,147,483,647. Longs have the additional feature of enjoying the fastest mathematic manipulation in the computer.
We often need to do floating-point arithmetic, of course; and the preceeding three data types do not support this. So, we first turn to the Single precision data type, 32 bits long, which can handle very large (or very small) numbers.
By the way, Single literals without an actual fraction, are displayed oddly. Let's say you type the following number in Visual Basic:
When you move your cursor to another line, the number changes to
The value hasn't changed; that's just Visual Basic's way of representing a whole floating point number.
And, if more precision is required—for example, a large number also has a many-digit decimal fraction—the Double precision data type takes up 64 bits.
Floating-point arithmetic is great for problems in mathematics, like calculating an atomic weight or the vectors of an orbital trajectory. However, it is not good for money arithmetic. That's because some decimal fractions, like 0.5, which look nice and tidy in base 10, become irrational or repeating fractions when converted to base 2 binary fractions.
The solution is to use the Currency data type for money. As you can imagine, this data type actually multiplies any incoming values by an amount that allows you to store dollars and cents as a whole number, instead of as a fraction. It supports currencies other that ours, too; values are actually multiplied by 10,000 so even the miniscule lira can be worked with.
These variables are 64 bits wide, so, in spite of the scaling factor, they can hold very large sums—even Bill Gates' bank balance.
Numbers aren't the only kinds of data we work with. The Boolean data type comes into play when simple True or False values are all you need to store.
Any assignment you make to a Boolean variable will first be evaluated to a true or false conclusion. If the expression is numeric, zero becomes False and any other value becomes True.
If you are familiar with the C language, or C++, you may be surprised to learn that True in Visual Basic is equal to 1, when treated numerically (rather than -1). Try not to let it bother you!
Work in Visual Basic is accomplished by statements, which are individual instructions to the computer. They are gathered into sets called procedures. Procedures come in two types, subroutines and functions. Subroutines are simple blocks of code; functions, like their mathematical namesakes, accept incoming information and transform it to produce an output.
This is a simplification. In reality, subroutines can also return information, though in a slightly different way; and functions do not have to have incoming information. However, the syntax for subroutines and functions differs, slightly, as we'll see.
Visual Basic comes with hundreds of pre-written procedures that handle many mundane tasks, ranging from renaming a disk file to calculating a square root. This tutorial cannot list them all for you!
Instead, plan on browsing through the online help and reading Visual Basic reference books to find out what's available. You don't want to waste time inventing something that's all ready for you to use!
However, if you can't find a procedure to do the job you need, you will write it yourself. As soon as you do, it will appear (in your project) to be "just another" procedure, part of the library of procedures. It's calling sequence will appear to help you as you type; you can even supply a help topic to document it!
The full syntax for declaring a subroutine is as follows:
[Static] [Private|Public] Sub subname [(argumentlist)] [statementblock] [Exit Sub] [statementblock] End Sub
Let's take the elements one at a time.
If you supply this optional keyword, every local variable in this procedure will be Static, as described previously—that is, as if you had actually declared all the variables with that keyword. This is a holdover from some early versions of BASIC; I don't recommend you use the feature.
Private / Public
By supplying the keyword Public, you declare that this procedure will be visible—and callable—from anywhere else in the project. Private, on the other hand, exposes the procedure only to other procedures in the same module.
A good way to think about Public and Private is this: A module's Public components are like the knobs and buttons on the outside of the device. Private components are like the wires and screws inside it, that make it work. The Public components are part of your software device's interface; it's Private components are part of its implementation.
This keyword is short for "subroutine" and is required.
Replace this with the name of your subroutine. The name must follow the rules for Visual Basic identifiers, and I recommend that (in general) it should be a verb or verb phrase.
This is the information on which the subroutine will work. Items in this list may be incoming, outgoing, or both. The format of the argument list will be discussed shortly.
One or more properly-formed Visual Basic statements.
Visual Basic allows more than one exit point from a subroutine, and this statement is the way you specify where they are.
This statement signifies the end of the subroutine; it also indicates an exit point from the subroutine. The next statement to be executed after the End Sub, is the first statement following the one that invoked this subroutine. (If the subroutine was invoked by the Visual Basic system, as is sometimes the case, then control will return to the system.)
When a procedure is called, you usually want to pass it information to guide it in doing its job. This information is sometimes called "parameters", and, sometimes, "arguments". The two terms are synonymous (in computer programming, at least!).
In Visual Basic, there are two ways to pass arguments: by reference and by value.
These are the default in Visual Basic; so let's discuss them first.
Suppose you are at a car dealership, and you are making an offer on a new car. You write your proposal down on a piece of paper and hand it to the salesperson. He or she snickers, scratches out your offer, and writes a new, higher, value on the paper and hands it back.
That piece of paper represents a reference argument.
When you pass a value as a reference argument in Visual Basic, you pass it in the form of a variable. Internally, Visual Basic actually passes the address in memory of that variable. You can't tell the difference in code, but if the value of the reference argument is changed within the procedure, that change will be manifested in the original variable, too.
Here's an example. Suppose you have the following procedure in a project:
Public Sub PrintSquare (Number as Integer) MsgBox Number * Number Number = 42 End Sub
Okay, it's an odd bit of code but work with me. Number is the incoming argument, passed by value. The subroutine displays the square of the number (in a message box, which we'll study in the next chapter), and then inexplicably assigns the value, 42, in place of whatever was there before.
Now, suppose the following scrap of code in the same project is executed:
Number = 4 PrintSquare Number MsgBox Number
Two message boxes will appear, one after the other: The first will display "16", and the second will display "42". Why? Because the procedure PrintSquare had the side effect of changing the value of that argument.
By the way, there is a keyword, ByRef, that is used by programmers who wish to be very specific to the reader which arguments are passed by reference. Since this is the default situation in Visual Basic, it is not required.
Suppose you found a note in a bottle. It might have some information that you could use (or it might not), but it is a one-way message. You can even write on the paper, but the sender of the message will never know what you wrote.
Value arguments are like that. When you pass an argument by value, the system actually makes a copy of the information, and that's what is passed. Inside the procedure, the information looks like a perfectly normal variable, but it can be used only by the procedure. Should the procedure alter the value in the argument variable, the caller will never know it. Value arguments are used for two reasons:
To document that the procedure will not change the value. Visual Basic can emit more efficient code if this is the case.
If the procedure isn't going to change the value, anyway, the value passed can be an expression rather than a simple variable. And, even if it is a variable, it doesn't have to be the same data type; Visual Basic will convert, as needed.
Let's look at an example. Suppose we the PrintSquare procedure was written this way:
Public Sub PrintSquare (ByVal Number as Integer) MsgBox Number * Number Number = 42 End Sub
This time, if the same scrap of code is executed:
Number = 4 PrintSquare Number MsgBox Number
Two message boxes will appear, one after the other: The first will display "16", but the second will display the original value, "4". Why? Because a copy of Number was passed to PrintSquare, not the original; and a change made to it has no effect.
There is definitely more to the art of designing an argument set for a procedure, including such possibilities as optional arguments with default values; but this should hold us for now.
Inserting a Procedure
There are two ways to add a procedure to a module.
Simply start typing it at the bottom of the code window.
Use the Tools..Add Procedure dialog, as shown at the left.
For example, suppose we decide to write a simple delaying procedure, which might come in handy in any number of places. We decide to give it a verb name (DelayFor) and that we will pass it the number of seconds for which we wish to delay. It does not need to return a value, so we can implement it as a subroutine.
If we use the Add Procedure dialog to create the skeleton, we will still have to manually add the procedure argument. In order to pass whole and fractional seconds, the arguments should be a floating point number; Single is appropriate. Here is the framework:
Public Sub DelayFor (ByVal Seconds as Single) End Sub
As we learn a little more, we'll be able to fill this procedure in.
The Date data type if one of the exciting features of Visual Basic. It is a 64-bit floating-point value in which the date is stored in the integer portion (as the number of whole days since a base date) and the time of day is stored as the fractional portion. Noon, in other words, is stored as .5; 6:00 pm is stored as .75.
Assignments can be made to a variable of the Date type from almost any other type. Strings are especially cunning: if you, a human, can read it, VB can convert it. Here are several string representations of dates and times that Visual Basic could successfully convert:
- April 8, 1951 7:05 pm
- 4/8/51 19:05
- 8-Apr-1951 7:05 PM
Moreover, if you have told Windows that you live in Canada, the second example above would be read as "August 4, 1951". Ingenious!
So, what can you do with a Date when you have one? Well, for one thing, you can find out the current time. The built-in function Now returns the current time, down to the hundredth of a second, as a Date value.
If you want to calculate a future time, as we do in the DelayFor procedure, simply divide the number of seconds by the number of seconds in a minute, by the number of minutes in an hour, by the number of hours in a day, and add the result to Now:
Public Sub DelayFor (ByVal Seconds As Single) Dim Target As Date Target = Now + (Seconds / 60# / 60# / 24#) End Sub
While / Wend
The next piece of Visual Basic syntax we'll look at implements a simple loop, in which statements are executed as long as an expression evaluates to True.
Here's the syntax for the While statement:
While expression statementblock Wend
The expression is tested before the statementblock is ever executed; so it is possible, if the expression fails the first time, that the statementblock will never be executed.
There is, by the way, an odd restriction to While loops: You are expressly forbidden to GoTo a label placed within a While loop. Why would you want to? I don't know…but don't do it.
Now, this loop is a very simple structure. What if you want to exit before expression ever tests True? —Then, don't use this structure. There is another, the Do While / Loop statement, that is more sophisticated. This structure should be reserved for simpler needs.
So, what does this "expression" look like? Well, it can be as simple as a single variable being tested for True (or, non-zero); and it can be as complex as all the relational operators can make it. Here are the operators:
||Is a equal to 17?|
||Is a less than 17?|
||Is a greater than 17?|
||Is a less than or equal to 17?|
||Is a greater than or equal to 17?|
||Is a not equal to 17?|
||Is a greater than 5 and also less than 17?|
||Is a either less than 5, or greater than 17?|
We now have enough information to write a simple While loop:
Public Sub DelayFor (ByVal Seconds As Single) Dim Target As Date Target = Now + (Seconds / 60# / 60# / 24#) While Now < Target Wend End Sub
Now, all we have to figure out, is what, exactly, to do while waiting for the target time to arrive.
There are over a thousand built-in functions and procedures, known as statements. Much of you Visual Basic code will invoke these in order to get its work done.
Fortunately, you don't have to memorize them all. The Object Browser (click the F2 key, remember), provides access to the VB library, where these statements can all be found, organized by subject.
One of the statements you can find there is DoEvents. Windows is a multi-tasking operating system that combines elements of co-operative multi-tasking and preemptive multi-tasking. An application that calls DoEvents supports the preemptive aspect by allowing other applications to do what they need to do what they need to do before it continues to execute a time-consuming task.
DoEvents is the perfect way for us to complete the DelayFor procedure. That way, while our application is waiting for the delay period to end, other applications can continue to process:
Public Sub DelayFor (ByVal Seconds As Single) Dim Target As Date Target = Now + (Seconds / 60# / 60# / 24#) While Now < Target DoEvents Wend End Sub
Implement the DelayFor procedure as described above.
Documenting Your Project Modules
The Object Browser (made visible by pressing the F2 key) provides a central location for providing and locating documentation on all aspects of your project.
Looking at the sample browser at the left, notice that the description we gave our skeleton project can be read in the lower panel of the browser.
We can (and should!) also document each module and procedure in our application. This is the first line of documentation for a project. And the more internal documentation we provide, the less need there is for a paper manual that no one will use, anyway!
To document a module or procedure, simply right-click on the item you wish to document, and select Properties from the context menu. Then enter the desired description in the place provided.
Document the Globals module: DelayFor
Pause application for a specified amount of time, while allowing other applications in the system to execute.
Tracks the number of unnamed documents ("Document1") created in the current session of the application.