In This Chapter
If you’ve used other programming languages, much of the information in this chapter may sound familiar. However, VBA has a few unique wrinkles, so even experienced programmers may find some new information.
This chapter explores the VBA language elements, which are the keywords and control structures that you use to write VBA routines.
To get the ball rolling, take a look at a the following VBA Sub procedure. This simple procedure, which is stored in a VBA module, calculates the sum of the first 100 positive integers. When the code finishes executing, the procedure displays a message with the result.
Sub VBA_Demo() ' This is a simple VBA Example Dim Total As Long, i As Long Total = 0 For i = 1 To 100 Total = Total + i Next i MsgBox Total End Sub
This procedure uses some common VBA language elements, including:
You will explore all of these language elements in subsequent sections of this chapter.
A comment is descriptive text embedded in your code and ignored by VBA. It’s a good idea to use comments liberally to describe what you’re doing because an instruction’s purpose isn’t always obvious.
You can use a complete line for your comment, or you can insert a comment after an instruction on the same line. A comment is indicated by an apostrophe. VBA ignores any text that follows an apostrophe — except when the apostrophe is contained within quotation marks — up until the end of the line. For example, the following statement doesn’t contain a comment, even though it has an apostrophe:
Msg ="Can't continue"
The following example shows a VBA procedure with three comments:
Sub CommentDemo() ' This procedure does nothing of value x = 0 'x represents nothingness ' Display the result MsgBox x End Sub
Although the apostrophe is the preferred comment indicator, you can also use the Rem keyword to mark a line as a comment. For example:
Rem -- The next statement prompts the user for a filename
The Rem keyword (short for Remark) is essentially a holdover from older versions of BASIC and is included in VBA for the sake of compatibility. Unlike the apostrophe, Rem can be written only at the beginning of a line, not on the same line as another instruction.
Following are a few general tips on making the best use of comments:
VBA’s main purpose is to manipulate data. Some data resides in objects, such as worksheet ranges. Other data is stored in variables that you create.
You can think of a variable as a named storage location in your computer’s memory. Variables can accommodate a wide variety of data types — from simple Boolean values (True or False) to large, double-precision values (see the following section). You assign a value to a variable by using the equal sign operator (more about this process in the upcoming section, “Assignment Statements”).
You make your life easier if you get into the habit of making your variable names as descriptive as possible. VBA does, however, have a few rules regarding variable names:
The following list contains some examples of assignment expressions that use various types of variables. The variable names are to the left of the equal sign. Each statement assigns the value to the right of the equal sign to the variable on the left.
x = 1 InterestRate = 0.075 LoanPayoffAmount = 243089.87 DataEntered = False x = x + 1 MyNum = YourNum * 1.25 UserName ="Bob Johnson" DateStarted = #12/14/2012#
VBA has many reserved words, which are words that you can’t use for variable or procedure names. If you attempt to use one of these words, you get an error message. For example, although the reserved word Next might make a very descriptive variable name, the following instruction generates a syntax error:
Next = 132
Unfortunately, syntax error messages aren’t always descriptive. If the Auto Syntax Check option is turned on, you get the error Compile error: Expected: variable. If Auto Syntax Check is turned off, attempting to execute this statement results in Compile error: Syntax error. It would be more helpful if the error message were something like Reserved word used as a variable. So if an instruction produces a strange error message, check the VBA Help system to ensure that your variable name doesn’t have a special use in VBA.
VBA makes life easy for programmers because it can automatically handle all the details involved in dealing with data. Some programming languages, however, are strictly typed, which means that the programmer must explicitly define the data type for every variable used.
Data type refers to how data is stored in memory — as integers, real numbers, strings, and so on. Although VBA can take care of data typing automatically, it does so at a cost: slower execution and less efficient use of memory. As a result, letting VBA handle data typing may present problems when you’re running large or complex applications. Another advantage of explicitly declaring your variables as a particular data type is that VBA can perform some additional error checking at the compile stage. These errors might otherwise be difficult to locate.
Table 3.1 lists VBA’s assortment of built-in data types. (Note that you can also define custom data types, which are covered later in this chapter in the section “User-Defined Data Types.”)
Table 3.1 VBA Built-In Data Types
Data Type | Bytes Used | Range of Values |
Byte | 1 byte | 0 to 255 |
Boolean | 2 bytes | True or False |
Integer | 2 bytes | –32,768 to 32,767 |
Long | 4 bytes | –2,147,483,648 to 2,147,483,647 |
Single | 4 bytes | –3.402823E38 to –1.401298E-45 (for negative values); 1.401298E-45 to 3.402823E38 (for positive values) |
Double | 8 bytes | –1.79769313486232E308 to –4.94065645841247E-324 (negative values); 4.94065645841247E-324 to 1.79769313486232E308 (for positive values) |
Currency | 8 bytes | –922,337,203,685,477.5808 to 922,337,203,685,477.5807 |
Decimal | 12 bytes | +/–79,228,162,514,264,337,593,543, 950,335 with no decimal point; +/–7.9228162514264337593543950335 with 28 places to the right of the decimal |
Date | 8 bytes | January 1, 0100 to December 31, 9999 |
Object | 4 bytes | Any object reference |
String (variable length) | 10 bytes + string length | 0 to approximately 2 billion characters |
String (fixed length) | Length of string | 1 to approximately 65,400 characters |
Variant (with numbers) | 16 bytes | Any numeric value up to the range of a double data type. It can also hold special values, such as Empty, Error, Nothing, and Null. |
Variant (with characters) | 22 bytes + string length | 0 to approximately 2 billion |
User-defined | Varies | Varies by element |
Generally, it’s best to use the data type that uses the smallest number of bytes yet still can handle all the data that will be assigned to it. When VBA works with data, execution speed is partially a function of the number of bytes that VBA has at its disposal. In other words, the fewer bytes used by data, the faster VBA can access and manipulate the data.
For worksheet calculation, Excel uses the Double data type, so that’s a good choice for processing numbers in VBA when you don’t want to lose any precision. For integer calculations, you can use the Integer type (which is limited to values less than or equal to 32,767). Otherwise, use the Long data type. In fact, using the Long data type even for values less than 32,767 is recommended because this data type may be a bit faster than using the Integer type. When dealing with Excel worksheet row numbers, you want to use the Long data type because the number of rows in a worksheet exceeds the maximum value for the Integer data type.
If you don’t declare the data type for a variable that you use in a VBA routine, VBA uses the default data type, Variant. Data stored as a Variant acts like a chameleon: It changes type, depending on what you do with it.
The following procedure demonstrates how a variable can assume different data types:
Sub VariantDemo() MyVar = True MyVar = MyVar * 100 MyVar = MyVar / 4 MyVar ="Answer:" & MyVar MsgBox MyVar End Sub
In the VariantDemo procedure, MyVar starts out as a Boolean. The multiplication operation converts it to an Integer. The division operation converts it to a Double. And finally, it’s concatenated with text to make it a String. The MsgBox statement displays the final string: Answer: -25.
To further demonstrate the potential problems in dealing with Variant data types, try executing this procedure:
Sub VariantDemo2() MyVar ="123" MyVar = MyVar + MyVar MyVar ="Answer:" & MyVar MsgBox MyVar End Sub
The message box displays Answer: 123123. This is probably not what you wanted. When dealing with variants that contain text strings, the + operator performs string concatenation.
You can use the VBA TypeName function to determine the data type of a variable. Here’s a modified version of the VariantDemo procedure. This version displays the data type of MyVar at each step.
Sub VariantDemo3() MyVar = True MsgBox TypeName(MyVar) MyVar = MyVar * 100 MsgBox TypeName(MyVar) MyVar = MyVar / 4 MsgBox TypeName(MyVar) MyVar ="Answer:" & MyVar MsgBox TypeName(MyVar) MsgBox MyVar End Sub
Thanks to VBA, the data type conversion of undeclared variables is automatic. This process may seem like an easy way out, but remember that you sacrifice speed and memory — and you run the risk of errors that you may not even know about.
Declaring each variable in a procedure before you use it is an excellent habit. Declaring a variable tells VBA its name and data type. Declaring variables provides two main benefits:
To force yourself to declare all the variables that you use, include the following as the first instruction in your VBA module:
Option Explicit
When this statement is present, VBA won’t even execute a procedure if it contains an undeclared variable name. VBA issues the error message shown in Figure 3.1, and you must declare the variable before you can proceed.
A variable’s scope determines in which modules and procedures you can use the variable. Table 3.2 lists the three ways in which a variable can be scoped.
Table 3.2 Variable Scope
Scope | To Declare a Variable with This Scope |
Single procedure | Include a Dim or Static statement within the procedure. |
Single module | Include a Dim or Private statement before the first procedure in a module. |
All modules | Include a Public statement before the first procedure in a module. |
We discuss each scope further in the following sections.
A local variable is a variable declared within a procedure. You can use local variables only in the procedure in which they’re declared. When the procedure ends, the variable no longer exists, and Excel frees up the memory that the variable used. If you need the variable to retain its value when the procedure ends, declare it as a Static variable. (See the section “Static variables,” later in this chapter.)
The most common way to declare a local variable is to place a Dim statement between a Sub statement and an End Sub statement. Dim statements usually are placed right after the Sub statement, before the procedure’s code.
The following procedure uses six local variables declared by using Dim statements:
Sub MySub() Dim x As Integer Dim First As Long Dim InterestRate As Single Dim TodaysDate As Date Dim UserName As String Dim MyValue ' - [The procedure's code goes here] - End Sub
Notice that the last Dim statement in the preceding example doesn’t declare a data type; it simply names the variable. As a result, that variable becomes a variant.
You also can declare several variables with a single Dim statement. For example:
Dim x As Integer, y As Integer, z As Integer Dim First As Long, Last As Double
If a variable is declared with a local scope, other procedures in the same module can use the same variable name, but each instance of the variable is unique to its own procedure.
In general, local variables are the most efficient because VBA frees up the memory that they use when the procedure ends.
Sometimes, you want a variable to be available to all procedures in a module. If so, just declare the variable before the module’s first procedure (outside of any procedures or functions).
In the following example, the Dim statement is the first instruction in the module. Both Procedure1 and Procedure2 have access to the CurrentValue variable.
Dim CurrentValue as Long Sub Procedure1() ' - [Code goes here] - End Sub Sub Procedure2() ' - [Code goes here] - End Sub
The value of a module-wide variable retains its value when a procedure ends normally (that is, when it reaches the End Sub or End Function statement). An exception is if the procedure is halted with an End statement. When VBA encounters an End statement, all variables in all modules lose their values.
To make a variable available to all the procedures in all the VBA modules in a project, declare the variable at the module level (before the first procedure declaration) by using the Public keyword rather than Dim. Here’s an example:
Public CurrentRate as Long
The Public keyword makes the CurrentRate variable available to any procedure in the VBA project, even those in other modules in the project. You must insert this statement before the first procedure in a module (any module). This type of declaration must appear in a standard VBA module, not in a code module for a sheet or a UserForm.
Static variables are a special case. They’re declared at the procedure level, and they retain their value when the procedure ends normally. However, if the procedure is halted by an End statement, static variables do lose their values. Note that an End statement is not the same as an End Sub statement.
You declare static variables by using the Static keyword:
Sub MySub() Static Counter as Long '- [Code goes here] - End Sub
A variable’s value may change while a procedure is executing (that’s why it’s called a variable). Sometimes, you need to refer to a named value or string that never changes: a constant.
Using constants throughout your code in place of hard-coded values or strings is an excellent programming practice. For example, if your procedure needs to refer to a specific value (such as an interest rate) several times, it’s better to declare the value as a constant and use the constant’s name rather than its value in your expressions. This technique not only makes your code more readable, it also makes it easier to change should the need arise — you have to change only one instruction rather than several.
You declare constants with the Const statement. Here are some examples:
Const NumQuarters as Integer = 4 Const Rate = .0725, Period = 12 Const ModName as String ="Budget Macros" Public Const AppName as String ="Budget Application"
The second example doesn’t declare a data type. Consequently, VBA determines the data type from the value. The Rate variable is a Double, and the Period variable is an Integer. Because a constant never changes its value, you normally want to declare your constants as a specific data type.
Like variables, constants also have a scope. If you want a constant to be available within a single procedure only, declare it after the Sub or Function statement to make it a local constant. To make a constant available to all procedures in a module, declare it before the first procedure in the module. To make a constant available to all modules in the workbook, use the Public keyword and declare the constant before the first procedure in a module. For example:
Public Const InterestRate As Double = 0.0725
Excel and VBA make available many predefined constants, which you can use without declaring. In fact, you don’t even need to know the value of these constants to use them. The macro recorder generally uses constants rather than actual values. The following procedure uses a built-in constant (xlLandscape) to set the page orientation to landscape for the active sheet:
Sub SetToLandscape() ActiveSheet.PageSetup.Orientation = xlLandscape End Sub
It’s often useful to record a macro just to discover the various constants that can be used. And, if you have the AutoList Members option turned on, you can often get some assistance while you enter your code (see Figure 3.2). In many cases, VBA lists all the constants that you can assign to a property.
The actual value for xlLandscape is 2 (which you can discover by using the Immediate window). The other built-in constant for changing paper orientation is xlPortrait, which has a value of 1. Obviously, if you use the built-in constants, you don’t really need to know their values.
Like Excel, VBA can manipulate both numbers and text (strings). There are two types of strings in VBA:
Each character in a string requires 1 byte of storage, plus a small amount of storage for the header of each string. When you declare a variable with a Dim statement as data type String, you can specify the length if you know it (that is, a fixed-length string), or you can let VBA handle it dynamically (a variable-length string).
In the following example, the MyString variable is declared to be a string with a maximum length of 50 characters. YourString is also declared as a string; but it’s a variable-length string, so its length is not fixed.
Dim MyString As String * 50 Dim YourString As String
You can use a string variable to store a date, but if you do, it’s not a real date (meaning you can’t perform date calculations with it). Using the Date data type is a better way to work with dates.
A variable defined as a date uses 8 bytes of storage and can hold dates ranging from January 1, 0100, to December 31, 9999. That’s a span of nearly 10,000 years — more than enough for even the most aggressive financial forecast! The Date data type is also useful for storing time-related data. In VBA, you specify dates and times by enclosing them between two hash marks (#).
Here are some examples of declaring variables and constants as Date data types:
Dim Today As Date Dim StartTime As Date Const FirstDay As Date = #1/1/2013# Const Noon = #12:00:00#
If you use a message box to display a date, it’s displayed according to your system’s short date format. Similarly, a time is displayed according to your system’s time format (either 12- or 24-hour). You can modify these system settings by using the Regional Settings option in the Windows Control Panel.
An assignment statement is a VBA instruction that evaluates an expression and assigns the result to a variable or an object. Excel’s Help system defines expression as “a combination of keywords, operators, variables, and constants that yields a string, number, or object. An expression can perform a calculation, manipulate characters, or test data.”
Much of the work done in VBA involves developing (and debugging) expressions. If you know how to create formulas in Excel, you’ll have no trouble creating expressions in VBA. With a worksheet formula, Excel displays the result in a cell. The result of a VBA expression, on the other hand, can be assigned to a variable or used as a property value.
VBA uses the equal sign (=) as its assignment operator. The following are examples of assignment statements (the expressions are to the right of the equal sign):
x = 1 x = x + 1 x = (y * 2) / (z * 2) FileOpen = True FileOpen = Not FileOpen Range("TheYear").Value = 2010
Often, expressions use functions. These functions can be built-in VBA functions, Excel’s worksheet functions, or custom functions that you develop in VBA. We discuss built-in VBA functions later in this chapter (see the upcoming section “Built-in Functions”).
Operators play a major role in VBA. Familiar operators describe mathematical operations, including addition (+), multiplication (*), division (/), subtraction (–), exponentiation (^), and string concatenation (&). Less familiar operators are the backslash () operator (used in integer division) and the Mod operator (used in modulo arithmetic). The Mod operator returns the remainder of one number divided by another. For example, the following expression returns 2:
17 Mod 3
VBA also supports the same comparison operators used in Excel formulas: equal to (=), greater than (>), less than (<), greater than or equal to (>=), less than or equal to (<=), and not equal to (<>).
With one exception, the order of precedence for operators in VBA is exactly the same as in Excel (see Table 3.3). And, of course, you can use parentheses to change the natural order of precedence.
Table 3.3 Operator Precedence
Operator | Operation | Order of Precedence |
^ | Exponentiation | 1 |
* and / | Multiplication and division | 2 |
+ and - | Addition and subtraction | 3 |
& | Concatenation | 4 |
=, <, >, <=, >=, <> | Comparison | 5 |
In the statement that follows, x is assigned the value 10 because the multiplication operator has a higher precedence than the addition operator:
x = 4 + 3 * 2
To avoid ambiguity, you may prefer to write the statement as follows:
x = 4 + (3 * 2)
In addition, VBA provides a full set of logical operators, shown in Table 3.4. For complete details on these operators (including examples), use the VBA Help system.
Table 3.4 VBA Logical Operators
Operator | What It Does |
Not | Performs a logical negation on an expression |
And | Performs a logical conjunction on two expressions |
Or | Performs a logical disjunction on two expressions |
Xor | Performs a logical exclusion on two expressions |
Eqv | Performs a logical equivalence on two expressions |
Imp | Performs a logical implication on two expressions |
The following instruction uses the Not operator to toggle the gridline display in the active window. The DisplayGridlines property takes a value of either True or False. Therefore, using the Not operator changes False to True and True to False.
ActiveWindow.DisplayGridlines = Not ActiveWindow.DisplayGridlines
The following expression performs a logical And operation. The MsgBox statement displays True only when Sheet1 is the active sheet and the active cell is in Row 1. If either or both of these conditions aren’t true, the MsgBox statement displays False.
MsgBox ActiveSheet.Name ="Sheet1" And ActiveCell.Row = 1
The following expression performs a logical Or operation. The MsgBox statement displays True when either Sheet1 or Sheet2 is the active sheet.
MsgBox ActiveSheet.Name ="Sheet1" Or ActiveSheet.Name ="Sheet2"
An array is a group of elements of the same type that have a common name. You refer to a specific element in the array by using the array name and an index number. For example, you can define an array of 12 string variables so that each variable corresponds to the name of a month. If you name the array MonthNames, you can refer to the first element of the array as MonthNames(0), the second element as MonthNames(1), and so on, up to MonthNames(11).
You declare an array with a Dim or Public statement, just as you declare a regular variable. You can also specify the number of elements in the array. You do so by specifying the first index number, the keyword To, and the last index number — all inside parentheses. For example, here’s how to declare an array comprising exactly 100 integers:
Dim MyArray(1 To 100) As Integer
By default, VBA assumes zero-based arrays. If you would like VBA to assume that 1 is the lower index for all arrays that declare only the upper index, include the following statement before any procedures in your module:
Option Base 1
The array examples in the preceding section are one-dimensional arrays. VBA arrays can have up to 60 dimensions, although you’ll rarely need more than three dimensions (a 3-D array). The following statement declares a 100-integer array with two dimensions (2-D):
Dim MyArray(1 To 10, 1 To 10) As Integer
You can think of the preceding array as occupying a 10-x-10 matrix. To refer to a specific element in a 2-D array, you need to specify two index numbers. For example, here’s how you can assign a value to an element in the preceding array:
MyArray(3, 4) = 125
Following is a declaration for a 3-D array that contains 1,000 elements (visualize this array as a cube):
Dim MyArray(1 To 10, 1 To 10, 1 To 10) As Integer
Reference an item in the array by supplying three index numbers:
MyArray(4, 8, 2) = 0
A dynamic array doesn’t have a preset number of elements. You declare a dynamic array with a blank set of parentheses:
Dim MyArray() As Integer
Before you can use a dynamic array in your code, however, you must use the ReDim statement to tell VBA how many elements are in the array. You can use a variable to assign the number of elements in an array. Often the value of the variable isn’t known until the procedure is executing. For example, if the variable x contains a number, you can define the array’s size by using this statement:
ReDim MyArray (1 To x)
You can use the ReDim statement any number of times, changing the array’s size as often as you need to. When you change an array’s dimensions the existing values are destroyed. If you want to preserve the existing values, use ReDim Preserve. For example:
ReDim Preserve MyArray (1 To y)
Arrays crop up later in this chapter when we discuss looping (see the section “Looping blocks of instructions”).
An object variable is a variable that represents an entire object, such as a range or a worksheet. Object variables are important for two reasons:
Object variables, like normal variables, are declared with the Dim or Public statement. For example, the following statement declares InputArea as a Range object variable:
Dim InputArea As Range
Use the Set keyword to assign an object to the variable. For example:
Set InputArea = Range("C16:E16")
To see how object variables simplify your code, examine the following procedure, which doesn’t use an object variable:
Sub NoObjVar() Worksheets("Sheet1").Range("A1").Value = 124 Worksheets("Sheet1").Range("A1").Font.Bold = True Worksheets("Sheet1").Range("A1").Font.Italic = True Worksheets("Sheet1").Range("A1").Font.Size = 14 Worksheets("Sheet1").Range("A1").Font.Name ="Cambria" End Sub
This routine enters a value into cell A1 of Sheet1 on the active workbook, applies some formatting, and changes the fonts and size. That’s a lot of typing. To reduce wear and tear on your fingers (and make your code more efficient), you can condense the routine with an object variable:
Sub ObjVar() Dim MyCell As Range Set MyCell = Worksheets("Sheet1").Range("A1") MyCell.Value = 124 MyCell.Font.Bold = True MyCell.Font.Italic = True MyCell.Font.Size = 14 MyCell.Font.Name ="Cambria" End Sub
After the variable MyCell is declared as a Range object, the Set statement assigns an object to it. Subsequent statements can then use the simpler MyCell reference in place of the lengthy Worksheets("Sheet1").Range("A1") reference.
The true value of object variables will become apparent when we discuss looping later in this chapter.
VBA lets you create custom, or user-defined, data types. A user-defined data type can ease your work with some types of data. For example, if your application deals with customer information, you may want to create a user-defined data type named CustomerInfo:
Type CustomerInfo Company As String Contact As String RegionCode As Long Sales As Double End Type
After you create a user-defined data type, you use a Dim statement to declare a variable as that type. Usually, you define an array. For example:
Dim Customers(1 To 100) As CustomerInfo
Each of the 100 elements in this array consists of four components (as specified by the user-defined data type, CustomerInfo). You can refer to a particular component of the record as follows:
Customers(1).Company ="Acme Tools" Customers(1).Contact ="Tim Robertson" Customers(1).RegionCode = 3 Customers(1).Sales = 150674.98
You can also work with an element in the array as a whole. For example, to copy the information from Customers(1) to Customers(2), use this instruction:
Customers(2) = Customers(1)
The preceding example is equivalent to the following instruction block:
Customers(2).Company = Customers(1).Company Customers(2).Contact = Customers(1).Contact Customers(2).RegionCode = Customers(1).RegionCode Customers(2).Sales = Customers(1).Sales
Like most programming languages, VBA has a variety of built-in functions that simplify calculations and operations. Many VBA functions are similar (or identical) to Excel worksheet functions. For example, the VBA function UCase, which converts a string argument to uppercase, is equivalent to the Excel worksheet function UPPER.
You use functions in VBA expressions in much the same way that you use functions in worksheet formulas. Here’s a simple procedure that calculates the square root of a variable (using the VBA Sqr function), stores the result in another variable, and then displays the result:
Sub ShowRoot() Dim MyValue As Double Dim SquareRoot As Double MyValue = 25 SquareRoot = Sqr(MyValue) MsgBox SquareRoot End Sub
The VBA Sqr function is equivalent to the Excel SQRT worksheet function.
You can use many (but not all) of Excel’s worksheet functions in your VBA code. The WorksheetFunction object, which is contained in the Application object, holds all the worksheet functions that you can call from your VBA procedures.
To use a worksheet function in a VBA statement, just precede the function name with:
Application.WorksheetFunction
The following example demonstrates how to use an Excel worksheet function in a VBA procedure. Excel’s infrequently used ROMAN function converts a decimal number into a roman numeral.
Sub ShowRoman() Dim DecValue As Long Dim RomanValue As String DecValue = 1939 RomanValue = Application.WorksheetFunction.Roman(DecValue) MsgBox RomanValue End Sub
When you execute this procedure, the MsgBox function displays the string MCMXXXIX.
Keep in mind that you can’t use worksheet functions that have an equivalent VBA function. For example, VBA can’t access the Excel SQRT worksheet function because VBA has its own version of that function: Sqr. Therefore, the following statement generates an error:
MsgBox Application.WorksheetFunction.Sqrt(123) 'error
As an Excel programmer, you’ll spend a lot of time working with objects and collections. Therefore, you want to know the most efficient ways to write your code to manipulate these objects and collections. VBA offers two important constructs that can simplify working with objects and collections:
The With-End With construct enables you to perform multiple operations on a single object. To start understanding how the With-End With construct works, examine the following procedure, which modifies six properties of a selection’s formatting (the selection is assumed to be a Range object):
Sub ChangeFont1() Selection.Font.Name ="Cambria" Selection.Font.Bold = True Selection.Font.Italic = True Selection.Font.Size = 12 Selection.Font.Underline = xlUnderlineStyleSingle Selection.Font.ThemeColor = xlThemeColorAccent1 End Sub
You can rewrite this procedure using the With-End With construct. The following procedure performs exactly like the preceding one:
Sub ChangeFont2() With Selection.Font .Name ="Cambria" .Bold = True .Italic = True .Size = 12 .Underline = xlUnderlineStyleSingle .ThemeColor = xlThemeColorAccent1 End With End Sub
Some people think that the second incarnation of the procedure is more difficult to read. Remember, though, that the objective is increased speed. Although the first version may be more straightforward and easier to understand, a procedure that uses the With-End With construct to change several properties of an object can be faster than the equivalent procedure that explicitly references the object in each statement.
Recall from the preceding chapter that a collection is a group of related objects. For example, the Workbooks collection is a collection of all open Workbook objects. You can also work with many other collections.
Suppose that you want to perform some action on all objects in a collection. Or suppose that you want to evaluate all objects in a collection and take action under certain conditions. These occasions are perfect for the For Each-Next construct because you don’t have to know how many elements are in a collection to use the For Each-Next construct.
The syntax of the For Each-Next construct is:
For Each element In collection [instructions] [Exit For] [instructions] Next [element]
The following procedure uses the For Each-Next construct with the Worksheets collection in the active workbook. When you execute the procedure, the MsgBox function displays each worksheet’s Name property. (If five worksheets are in the active workbook, the MsgBox function is called five times.)
Sub CountSheets() Dim Item as Worksheet For Each Item In ActiveWorkbook.Worksheets MsgBox Item.Name Next Item End Sub
The next example uses For Each-Next to cycle through all objects in the Windows collection and count the number of windows that are hidden:
Sub HiddenWindows() Dim Cnt As Integer Dim Win As Window Cnt = 0 For Each Win In Windows If Not Win.Visible Then Cnt = Cnt + 1 Next Win MsgBox Cnt &" hidden windows." End Sub
For each window, if the window is hidden, the Cnt variable is incremented. When the loop ends, the message box displays the value of Cnt.
Here’s an example that closes all workbooks except the active workbook. This procedure uses the If-Then construct to evaluate each workbook in the Workbooks collection:
Sub CloseInactive() Dim Book as Workbook For Each Book In Workbooks If Book.Name <> ActiveWorkbook.Name Then Book.Close Next Book End Sub
A common use for the For Each-Next construct is to loop through all cells in a range. The next example of For Each-Next is designed to be executed after the user selects a range of cells. Here, the Selection object acts as a collection that consists of Range objects because each cell in the selection is a Range object. The procedure evaluates each cell and uses the VBA UCase function to convert its contents to uppercase. (Numeric cells are not affected.)
Sub MakeUpperCase() Dim Cell as Range For Each Cell In Selection Cell.Value = UCase(Cell.Value) Next Cell End Sub
VBA provides a way to exit a For-Next loop before all the elements in the collection are evaluated. Do this with an Exit For statement. The example that follows selects the first negative value in Row 1 of the active sheet:
Sub SelectNegative() Dim Cell As Range For Each Cell In Range("1:1") If Cell.Value < 0 Then Cell.Select Exit For End If Next Cell End Sub
This example uses an If-Then construct to check the value of each cell. If a cell is negative, it’s selected, and then the loop ends when the Exit For statement is executed.
Some VBA procedures start at the top and progress line by line to the bottom. Macros that you record, for example, always work in this fashion. Often, however, you need to control the flow of your routines by skipping over some statements, executing some statements multiple times, and testing conditions to determine what the routine does next.
The preceding section describes the For Each-Next construct, which is a type of loop. This section discusses the additional ways of controlling the execution of your VBA procedures:
The most straightforward way to change the flow of a program is to use a GoTo statement. This statement simply transfers program execution to a new instruction, which must be preceded by a label (a text string followed by a colon, or a number with no colon). VBA procedures can contain any number of labels, but a GoTo statement can’t branch outside a procedure.
The following procedure uses the VBA InputBox function to get the user’s name. If the name is not Howard, the procedure branches to the WrongName label and ends. Otherwise, the procedure executes some additional code. The Exit Sub statement causes the procedure to end.
Sub GoToDemo() UserName = InputBox("Enter Your Name:") If UserName <>"Howard" Then GoTo WrongName MsgBox ("Welcome Howard...") ' -[More code here] - Exit Sub WrongName: MsgBox"Sorry. Only Howard can run this macro." End Sub
This simple procedure works, but it’s not an example of good programming. In general, you should use the GoTo statement only when you have no other way to perform an action. In fact, the only time you really need to use a GoTo statement in VBA is for error handling (refer to Chapter 4).
Finally, it goes without saying that the preceding example is not intended to demonstrate an effective security technique!
Perhaps the most commonly used instruction grouping in VBA is the If-Then construct. This common instruction is one way to endow your applications with decision-making capability. Good decision-making is the key to writing successful programs.
The basic syntax of the If-Then construct is:
If condition Then true_instructions [Else false_instructions]
The If-Then construct is used to execute one or more statements conditionally. The Else clause is optional. If included, the Else clause lets you execute one or more instructions when the condition that you’re testing isn’t True.
The following procedure demonstrates an If-Then structure without an Else clause. The example deals with time, and VBA uses a date-and-time serial number system similar to Excel’s. The time of day is expressed as a fractional value — for example, noon is represented as .5. The VBA Time function returns a value that represents the time of day, as reported by the system clock. In the following example, a message is displayed if the time is before noon. If the current system time is greater than or equal to .5, the procedure ends, and nothing happens.
Sub GreetMe1() If Time < 0.5 Then MsgBox"Good Morning" End Sub
Another way to code this routine is to use multiple statements, as follows:
Sub GreetMe1a() If Time < 0.5 Then MsgBox"Good Morning" End If End Sub
Note that the If statement has a corresponding End If statement. In this example, only one statement is executed if the condition is True. You can, however, place any number of statements between the If and End If statements.
If you want to display a different greeting when the time of day is after noon, add another If-Then statement, like so:
Sub GreetMe2() If Time < 0.5 Then MsgBox"Good Morning" If Time >= 0.5 Then MsgBox"Good Afternoon" End Sub
Notice that we used >= (greater than or equal to) for the second If-Then statement. This covers the remote chance that the time is precisely 12:00 noon.
Another approach is to use the Else clause of the If-Then construct. For example,
Sub GreetMe3() If Time < 0.5 Then MsgBox"Good Morning" Else _ MsgBox"Good Afternoon" End Sub
Notice that we used the line continuation sequence; If-Then-Else is actually a single statement.
If you need to execute multiple statements based on the condition, use this form:
Sub GreetMe3a() If Time < 0.5 Then MsgBox"Good Morning" ' Other statements go here Else MsgBox"Good Afternoon" ' Other statements go here End If End Sub
If you need to expand a routine to handle three conditions (for example, morning, afternoon, and evening), you can use either three If-Then statements or a form that uses ElseIf. The first approach is simpler:
Sub GreetMe4() If Time < 0.5 Then MsgBox"Good Morning" If Time >= 0.5 And Time < 0.75 Then MsgBox"Good Afternoon" If Time >= 0.75 Then MsgBox"Good Evening" End Sub
The value 0.75 represents 6:00 p.m. — three-quarters of the way through the day and a good point at which to call it an evening.
In the preceding examples, every instruction in the procedure gets executed, even if the first condition is satisfied (that is, it’s morning). A more efficient procedure would include a structure that ends the routine when a condition is found to be True. For example, it might display the Good Morning message in the morning and then exit without evaluating the other, superfluous conditions. True, the difference in speed is inconsequential when you design a procedure as small as this routine. But for more complex applications, you need another syntax:
If condition Then [true_instructions] [ElseIf condition-n Then [alternate_instructions]] [Else [default_instructions]] End If
Here’s how you can use this syntax to rewrite the GreetMe procedure:
Sub GreetMe5() If Time < 0.5 Then MsgBox"Good Morning" ElseIf Time >= 0.5 And Time < 0.75 Then MsgBox"Good Afternoon" Else MsgBox"Good Evening" End If End Sub
With this syntax, when a condition is True, the conditional statements are executed, and the If-Then construct ends. In other words, the extraneous conditions aren’t evaluated. Although this syntax makes for greater efficiency, some find the code to be more difficult to understand.
The following procedure demonstrates yet another way to code this example. It uses nested If-Then-Else constructs (without using ElseIf). This procedure is efficient and also easy to understand. Note that each If statement has a corresponding End If statement.
Sub GreetMe6() If Time < 0.5 Then MsgBox"Good Morning" Else If Time >= 0.5 And Time < 0.75 Then MsgBox"Good Afternoon" Else If Time >= 0.75 Then MsgBox"Good Evening" End If End If End If End Sub
The following is another example that uses the simple form of the If-Then construct. This procedure prompts the user for a value for Quantity and then displays the appropriate discount based on that value. Note that Quantity is declared as a Variant data type. This is because Quantity contains an empty string (not a numeric value) if InputBox is cancelled. To keep the procedure simple, it doesn’t perform any other error checking. For example, it doesn’t ensure that the quantity entered is a non-negative numeric value.
Sub Discount1() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox("Enter Quantity:") If Quantity ="" Then Exit Sub If Quantity >= 0 Then Discount = 0.1 If Quantity >= 25 Then Discount = 0.15 If Quantity >= 50 Then Discount = 0.2 If Quantity >= 75 Then Discount = 0.25 MsgBox"Discount:" & Discount End Sub
Notice that each If-Then statement in this procedure is always executed, and the value for Discount can change. The final value, however, is the desired value.
The following procedure is the previous one rewritten to use the alternate syntax. In this case, the procedure ends after executing the True instruction block.
Sub Discount2() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox("Enter Quantity:") If Quantity ="" Then Exit Sub If Quantity >= 0 And Quantity < 25 Then Discount = 0.1 ElseIf Quantity < 50 Then Discount = 0.15 ElseIf Quantity < 75 Then Discount = 0.2 Else Discount = 0.25 End If MsgBox"Discount:" & Discount End Sub
The Select Case construct is useful for choosing among three or more options. This construct also works with two options and is a good alternative to If-Then-Else. The syntax for Select Case is as follows:
Select Case testexpression [Case expressionlist-n [instructions-n]] [Case Else [default_instructions]] End Select
The following example of a Select Case construct shows another way to code the GreetMe examples presented in the preceding section:
Sub GreetMe() Dim Msg As String Select Case Time Case Is < 0.5 Msg ="Good Morning" Case 0.5 To 0.75 Msg ="Good Afternoon" Case Else Msg ="Good Evening" End Select MsgBox Msg End Sub
And here’s a rewritten version of the Discount example using a Select Case construct. This procedure assumes that Quantity is always an integer value. For simplicity, the procedure performs no error checking.
Sub Discount3() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox("Enter Quantity:") Select Case Quantity Case"" Exit Sub Case 0 To 24 Discount = 0.1 Case 25 To 49 Discount = 0.15 Case 50 To 74 Discount = 0.2 Case Is >= 75 Discount = 0.25 End Select MsgBox"Discount:" & Discount End Sub
The Case statement also can use a comma to separate multiple values for a single case. The following procedure uses the VBA WeekDay function to determine whether the current day is a weekend (that is, the Weekday function returns 1 or 7). The procedure then displays an appropriate message.
Sub GreetUser1() Select Case Weekday(Now) Case 1, 7 MsgBox"This is the weekend" Case Else MsgBox"This is not the weekend" End Select End Sub
The following example shows another way to code the previous procedure:
Sub GreetUser2() Select Case Weekday(Now) Case 2, 3, 4, 5, 6 MsgBox"This is not the weekend" Case Else MsgBox"This is the weekend" End Select End Sub
Here’s another way to code the procedure, using the To keyword to specify a range of values.
Sub GreetUser3() Select Case Weekday(Now) Case 2 To 6 MsgBox"This is not the weekend" Case Else MsgBox"This is the weekend" End Select End Sub
And to demonstrate the flexibility of VBA, a final example in which each case is evaluated until one of the expressions evaluates to True:
Sub GreetUser4() Select Case True Case Weekday(Now) = 1 MsgBox"This is the weekend" Case Weekday(Now) = 7 MsgBox"This is the weekend" Case Else MsgBox"This is not the weekend" End Select End Sub
Any number of instructions can be written below each Case statement, and they’re all executed if that case evaluates to True. If you use only one instruction per case, as in the preceding example, you might want to put the instruction on the same line as the Case keyword (but don’t forget the VBA statement-separator character, the colon). This technique makes the code more compact. For example:
Sub Discount3() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox("Enter Quantity:") Select Case Quantity Case"": Exit Sub Case 0 To 24: Discount = 0.1 Case 25 To 49: Discount = 0.15 Case 50 To 74: Discount = 0.2 Case Is >= 75: Discount = 0.25 End Select MsgBox"Discount:" & Discount End Sub
Select Case structures can also be nested. The following procedure, for example, uses the VBA TypeName function to determine what is selected (a range, nothing, or anything else). If a range is selected, the procedure executes a nested Select Case and tests for the number of cells in the range. If one cell is selected, it displays One cell is selected. Otherwise, it displays a message with the number of selected rows.
Sub SelectionType() Select Case TypeName(Selection) Case"Range" Select Case Selection.Count Case 1 MsgBox"One cell is selected" Case Else MsgBox Selection.Rows.Count &" rows" End Select Case"Nothing" MsgBox"Nothing is selected" Case Else MsgBox"Something other than a range" End Select End Sub
This procedure also demonstrates the use of Case Else, a catch-all case. You can nest Select Case constructs as deeply as you need, but make sure that each Select Case statement has a corresponding End Select statement.
This procedure demonstrates the value of using indentation in your code to clarify the structure. For example, take a look at the same procedure without the indentations:
Sub SelectionType() Select Case TypeName(Selection) Case"Range" Select Case Selection.Count Case 1 MsgBox"One cell is selected" Case Else MsgBox Selection.Rows.Count &" rows" End Select Case"Nothing" MsgBox"Nothing is selected" Case Else MsgBox"Something other than a range" End Select End Sub
Fairly incomprehensible, eh?
Looping is the process of repeating a block of instructions. You might know the number of times to loop, or the number may be determined by the values of variables in your program.
The following code, which enters consecutive numbers into a range, demonstrates what is considered to be a bad loop. The procedure uses two variables to store a starting value (StartVal) and the total number of cells to fill (NumToFill). This loop uses the GoTo statement to control the flow. If the Cnt variable, which keeps track of how many cells are filled, is less than the value of NumToFill, the program control loops back to DoAnother.
Sub BadLoop() Dim StartVal As Integer Dim NumToFill As Integer Dim Cnt As Integer StartVal = 1 NumToFill = 100 ActiveCell.Value = StartVal Cnt = 1 DoAnother: ActiveCell.Offset(Cnt, 0).Value = StartVal + Cnt Cnt = Cnt + 1 If Cnt < NumToFill Then GoTo DoAnother Else Exit Sub End Sub
This procedure works as intended, so why is it an example of bad looping? Programmers generally frown on using a GoTo statement when not absolutely necessary. Using GoTo statements to loop is contrary to the concept of structured coding. (See the “What is structured programming?” sidebar.) A GoTo statement makes the code much more difficult to read because representing a loop using line indentations is almost impossible. In addition, this type of unstructured loop makes the procedure more susceptible to error. Furthermore, using lots of labels results in spaghetti code — code that appears to have little or no structure and has a tangled flow.
Because VBA has several structured looping commands, you almost never have to rely on GoTo statements for your decision-making.
The simplest type of a good loop is a For-Next loop. Its syntax is:
For counter = start To end [Step stepval] [instructions] [Exit For] [instructions] Next [counter]
Following is an example of a For-Next loop that doesn’t use the optional Step value or the optional Exit For statement. This routine executes the Sum = Sum + Sqr(Count) statement 100 times and displays the result — that is, the sum of the square roots of the first 100 integers.
Sub SumSquareRoots() Dim Sum As Double Dim Count As Integer Sum = 0 For Count = 1 To 100 Sum = Sum + Sqr(Count) Next Count MsgBox Sum End Sub
In this example, Count (the loop counter variable) starts out as 1 and increases by 1 each time the loop repeats. The Sum variable simply accumulates the square roots of each value of Count.
You can also use a Step value to skip some values in the loop. Here’s the same procedure rewritten to sum the square roots of the odd numbers between 1 and 100:
Sub SumOddSquareRoots() Dim Sum As Double Dim Count As Integer Sum = 0 For Count = 1 To 100 Step 2 Sum = Sum + Sqr(Count) Next Count MsgBox Sum End Sub
In this procedure, Count starts out as 1 and then takes on values of 3, 5, 7, and so on. The final value of Count used in the loop is 99. When the loop ends, the value of Count is 101.
A Step value in a For-Next loop can also be negative. The procedure that follows deletes Rows 2, 4, 6, 8, and 10 of the active worksheet:
Sub DeleteRows() Dim RowNum As Long For RowNum = 10 To 2 Step -2 Rows(RowNum).Delete Next RowNum End Sub
You may wonder why we used a negative Step value in the DeleteRows procedure. If you use a positive Step value, as shown in the following procedure, incorrect rows are deleted. That’s because the rows below a deleted row get a new row number. For example, when Row 2 is deleted, Row 3 becomes the new Row 2. Using a negative Step value ensures that the correct rows are deleted.
Sub DeleteRows2() Dim RowNum As Long For RowNum = 2 To 10 Step 2 Rows(RowNum).Delete Next RowNum End Sub
The following procedure performs the same task as the BadLoop example at the beginning of the “Looping blocks of instructions” section. We eliminate the GoTo statement, however, converting a bad loop into a good loop that uses the For-Next structure.
Sub GoodLoop() Dim StartVal As Integer Dim NumToFill As Integer Dim Cnt As Integer StartVal = 1 NumToFill = 100 For Cnt = 0 To NumToFill - 1 ActiveCell.Offset(Cnt, 0).Value = StartVal + Cnt Next Cnt End Sub
For-Next loops can also include one or more Exit For statements in the loop. When this statement is encountered, the loop terminates immediately and control passes to the statement following the Next statement of the current For-Next loop. The following example demonstrates the use of the Exit For statement. This procedure determines which cell has the largest value in Column A of the active worksheet:
Sub ExitForDemo() Dim MaxVal As Double Dim Row As Long MaxVal = Application.WorksheetFunction.Max(Range("A:A")) For Row = 1 To 1048576 If Cells(Row, 1).Value = MaxVal Then Exit For End If Next Row MsgBox"Max value is in Row" & Row Cells(Row, 1).Activate End Sub
The maximum value in the column is calculated by using the Excel MAX function, and the value is assigned to the MaxVal variable. The For-Next loop checks each cell in the column. If the cell being checked is equal to MaxVal, the Exit For statement terminates the loop and the statements following the Next statement are executed. These statements display the row of the maximum value and activate the cell.
The previous examples use relatively simple loops. But you can have any number of statements in the loop, and you can even nest For-Next loops inside other For-Next loops. Here’s an example that uses nested For-Next loops to initialize a 10 x 10 x 10 array with the value –1. When the procedure is finished, each of the 1,000 elements in MyArray contains –1.
Sub NestedLoops() Dim MyArray(1 to 10, 1 to 10, 1 to 10) Dim i As Integer, j As Integer, k As Integer For i = 1 To 10 For j = 1 To 10 For k = 1 To 10 MyArray(i, j, k) = -1 Next k Next j Next i ' [More code goes here] End Sub
This section describes another type of looping structure available in VBA. Unlike a For-Next loop, a Do While loop executes as long as a specified condition is met.
A Do While loop can have either of two syntaxes:
Do [While condition] [instructions] [Exit Do] [instructions] Loop
or
Do [instructions] [Exit Do] [instructions] Loop [While condition]
As you can see, VBA lets you put the While condition at the beginning or the end of the loop. The difference between these two syntaxes involves the point at which the condition is evaluated. In the first syntax, the contents of the loop may never be executed. In the second syntax, the statements inside the loop are always executed at least one time.
The following examples insert a series of dates into the active worksheet. The dates correspond to the days in the current month, and the dates are entered in a column beginning at the active cell.
The first example demonstrates a Do While loop that tests the condition at the beginning of the loop: The EnterDates1 procedure writes the dates of the current month to a worksheet column, beginning with the active cell.
Sub EnterDates1() ' Do While, with test at the beginning Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do While Month(TheDate) = Month(Date) ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop End Sub
This procedure uses a variable, TheDate, which contains the dates that are written to the worksheet. This variable is initialized with the first day of the current month. Inside the loop, the value of TheDate is entered into the active cell, TheDate is incremented, and the next cell is activated. The loop continues while the month of TheDate is the same as the month of the current date.
The following procedure has the same result as the EnterDates1 procedure, but it uses the second Do While loop syntax, which checks the condition at the end of the loop.
Sub EnterDates2() ' Do While, with test at the end Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop While Month(TheDate) = Month(Date) End Sub
Do While loops can also contain one or more Exit Do statements. When an Exit Do statement is encountered, the loop ends immediately and control passes to the statement following the Loop statement.
The Do Until loop structure is similar to the Do While structure. The difference is evident only when the condition is tested. In a Do While loop, the loop executes while the condition is True; in a Do Until loop, the loop executes until the condition is True.
Do Until also has two syntaxes:
Do [Until condition] [instructions] [Exit Do] [instructions] Loop
or
Do [instructions] [Exit Do] [instructions] Loop [Until condition]
The two examples that follow perform the same action as the Do While date entry examples in the previous section. The difference in these two procedures is where the condition is evaluated (at the beginning or the end of the loop). Here is the first example:
Sub EnterDates3() ' Do Until, with test at beginning Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do Until Month(TheDate) <> Month(Date) ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop End Sub
Here is the second example:
Sub EnterDates4() ' Do Until, with test at end Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do ActiveCell = TheDate TheDate = TheDate + 1s ActiveCell.Offset(1, 0).Activate Loop Until Month(TheDate) <> Month(Date) End Sub
The following example was originally presented for the Do While loop but has been rewritten to use a Do Until loop. The only difference is the line with the Do statement. This example makes the code a bit clearer because it avoids the negative required in the Do While example.
Sub DoUntilDemo1() Dim LineCt As Long Dim LineOfText As String Open"c:data extfile.txt" For Input As #1 LineCt = 0 Do Until EOF(1) Line Input #1, LineOfText Range("A1").Offset(LineCt, 0) = UCase(LineOfText) LineCt = LineCt + 1 Loop Close #1 End Sub
52.14.77.134