As in life, so in macros. Sometimes, you'll want to repeat an action a predetermined number of times: break six eggs to make an omelet, or create two new documents.
More often, though, you'll just repeat an action until a certain condition is met: break eggs until the pan is full, or buy two lottery tickets a week until you hit it big, or subtract five from every instance of a value in an Excel spreadsheet. In these situations, you don't know in advance when you'll triumph against the wretched odds of the lottery, or how many times the value will appear in the spreadsheet—your code must simply carry on until the condition is met.
In VBA, you use loops to repeat actions. VBA provides a number of ways to use loops in your code. In this chapter, you'll learn about the different types of loops and typical uses for each.
In this chapter you will learn to do the following:
To repeat an action or a series of actions in VBA, you could record the repetition itself into a macro by using the Macro Recorder (if the application you're using supports the Macro Recorder—remember that only Word and Excel do).
Or you could copy some code and paste it back into the macro multiple times to repeat the behavior. For example, you could record a macro containing the code for creating a new Word document based on the default template, open the macro in the Visual Basic Editor, and then copy this new-document code and paste it five times to create a procedure that makes six new documents.
It's almost always much better, however, to just write a loop block (structure) to repeat the commands as necessary.
Loops have several straightforward advantages over repetitive, redundant code:
That said, if you just need to repeat one or more actions two or three times in a procedure and that procedure will always need to repeat the action this same number of times, there's nothing wrong with hard-coding the procedure by repeating the code. It'll work fine, it's easy to do, and you won't have to spend time considering the logic of loops. The code will be longer and a tad harder to maintain, but that's no big deal in simple situations.
In VBA, a loop is a structure (block of code) that repeats a number of statements, looping back to the beginning of the structure once it has finished executing them. Each cycle of execution of a loop is called an iteration.
There are two basic categories of loops:
The execution of either type of loop is controlled by the loop invariant, also called the loop determinant. This can be either a numeric expression or a logical expression. Fixed-iteration loops typically use numeric expressions, whereas indefinite loops typically use logical expressions. For example, a fixed-iteration loop might specify that the loop will iterate five times, while an indefinite loop might continue iterating until the end of a document is reached.
Table 12.1 explains the types of loops that VBA provides.
For…loops execute for a fixed number of times. For…Next loops repeat for the number of times of your choosing, while For Each… Next loops execute once for each element in a specified VBA collection.
A For…Next loop repeats an action or a sequence of actions a given number of times. How many times it loops is specified by a counter variable. The counter variable can be hard-coded into the procedure, passed from an input box or dialog box, or passed from a value generated either by a different part of the procedure or by a different procedure.
The syntax for For…Next loops is as follows:
For counter = start To end [Step stepsize] [statements] [Exit For] [statements] Next [counter]
Here's what happens in a For…Next loop (refer to the syntax):
Table 12.2 explains the components of the For…Next loop syntax. As usual, brackets enclose optional items and italicized words are placeholders—elements in the code that are to be replaced by you, the programmer.
COMPONENT | DESCRIPTION |
Counter | A numeric variable or an expression that produces a number. By default, VBA increases the counter value by an increment of 1 with each iteration of the loop, but you can change this increment by using the optional Step keyword and stepsize argument. counter is required in the For statement and is optional in the Next statement, but it's a good idea to also include counter in the Next statement to make your code easy to read. This is particularly important when you're using multiple For…Next statements in the same procedure or nesting one For…Next statement within another. |
Start | A numeric variable or numeric expression giving the starting value for counter. |
End | A numeric variable or numeric expression giving the ending value for counter. |
Stepsize | A numeric variable or numeric expression specifying how much to increase or decrease the value of counter. To use stepsize, use the Step keyword and specify the stepsize variable. stepsize is 1 by default, but you can use any positive or negative value. |
Exit For | A statement for exiting a For…loop. |
Next | The keyword indicating the end of the loop. Again, you can specify the optional counter here to make your code clear. |
In a simple For…Next loop, you first specify a counter variable and the starting and ending values for it:
Dim i As Integer For i = 1 to 200
Here, i is the counter variable, 1 is the starting value, and 200 is the ending value. Because VBA by default increases the counter variable by 1 with each iteration of the loop, the counter variable in this example will count 1, 2, 3, and so on up to 200. Once the loop iterates enough times so the value in counter is 201, the looping ends and execution continues in the line below the loop's End statement.
You can also use the Step keyword to specify a different increment, either positive or negative; more on this in the next section.
i IS THE TRADITIONAL COUNTER VARIABLE NAME FOR for…next LOOPS
i is the classic integer counter variable used in a For…Next loop; after using i, the convention is to use j, k, l, m, and n for any subsequent counter variables (if you're adding nested loops within in i loop). These short variable names derive from the days of key-card computation, when memory was at a premium and longer names represented a significant extravagance. These days, computer memory is abundant, so using long variable names is common practice for most variables. But not with loop counters. Using i as the loop counter is pervasive, even in languages like Java and C++. So stick with i.
After the previous two statements (Dim and For), you specify whatever actions you want carried out within the loop, followed by the Next keyword to end the loop:
Application.StatusBar = _ "Please wait while Excel checks for nonuniform prices: " & i & "…" Next i
This code displays (on the status bar) Excel's progress in checking your spreadsheet for improbable values.
As another example, say you need to check every paragraph in Word documents you receive from contributors to make sure there's no unsuitable formatting. By using a loop that runs from 1 to the number of paragraphs in the active document, you can check each paragraph in turn and let the user view the progress in the status bar. The number of paragraphs in a document is stored in the Count property of the Paragraphs collection in the ActiveDocument object:
Dim i As Integer For i = 1 To ActiveDocument.Paragraphs.Count 'CheckParagraphForIllegalFormatting DoEvents Application.StatusBar = _ "Please wait while Word checks the formatting in " _ & " this document: Paragraph " & i & " out of " _ & ActiveDocument.Paragraphs.Count & "…" Selection.MoveDown Unit:=wdParagraph, _ Count:=1, Extend:=wdMove Next i
This code snippet executes a CheckParagraphForIllegalFormatting procedure. We've not yet written this procedure, so I just wrote a comment line indicating that the procedure needs to be called from inside this loop.
Next we use the DoEvents command. This allows multitasking. It interrupts the loop to see if something else is going on in the computer (the user typing something, the status bar in Word being updated, or whatever). This prevents your loop from hogging the computer's microprocessor.
Then the loop continues executing. The message is displayed in the status bar, indicating which paragraph out of the total number it's working on, and then the loop moves down a paragraph. When VBA reaches the Next statement, it increases the i counter by the default value, 1 (because no stepsize variable is specified in the For statement) and loops back to the For statement, where it compares the value of i to the value of ActiveDocument.Paragraphs.Count. The procedure continues to loop until i has reached the value of ActiveDocument.Paragraphs.Count, which is the final iteration of the loop. Notice here how the counter variable is used twice: first to keep track of the loop's iterations, but it's also used later within the loop to display the current paragraph number:
Paragraph " & i &
In a similar way you could use a simple For…Next loop to quickly build the structure of a timesheet or work log in Excel. The following statements use a For…Next loop to insert the labels 1.00 through 24:00 in the current column in the active sheet of the active workbook:
Dim i As Integer
For i = 1 To 24
ActiveCell.FormulaR1C1 = i & ":00"
ActiveCell.Offset(RowOffset:=1, ColumnOffset:=0).Select
Next i
Here, the ActiveCell.FormulaR1Ci statement inserts the automatically increased string for the counter i together with a colon and two zeroes (to create a time format). The ActiveCell.Offset(RowOffset:=1, ColumnOffset:=0).Select statement selects the cell in the next row and the same column. The loop runs from i = 1 to i = 24 and stops when the automatic increase takes i to 25. Again, the counter variable is used within the loop. This is quite common.
If increasing the counter variable by the default 1 doesn't suit your purpose, you can use the Step keyword to specify a different increment or decrement.
For example, the following statement increases the counter variable by 20, so the sequence is 0, 20, 40, 60, 80, 100:
For i = 0 to 100 Step 20
You can also decrement by specifying a negative Step value:
For i = 1000 to 0 Step -100
This statement produces the sequence 1000, 900, 800, and so on, down to 0.
Instead of the “x out of y” countdown given in the example in the previous section, you could produce a countdown running from ActiveDocument.Paragraphs.Count to zero:
Dim i As Integer
For i = ActiveDocument.Paragraphs.Count To 0 Step -1
CheckParagraphForIllegalFormatting
Application.StatusBar = _
"Please wait while Word checks the formatting in this document: " & i
Selection.MoveDown Unit:=wdParagraph, Count:=1, Extend:=wdMove Next i
Sometimes you'll be able to hard-code the number of iterations into a For…Next loop (six eggs). You'll know the number of iterations when writing your code, so you can just type in the end condition number, like the 100 here:
For i = 0 to 100
Other times, though, you can't know in advance how many loop iterations are needed. This information only becomes available during program execution (called runtime) rather than when you're writing the code (called design time).
Often you'll take a number from another operation during execution, such as the ActiveDocument.Paragraphs.Count property in the previous example.
You want to use this macro with many documents in the future. The number of paragraphs in various documents is different; it varies. So you can't know when writing your code how many times it should loop. Your macro itself has to gather that information at runtime.
Frequently you ask the user to specify the number of loop repetitions. The easiest way of doing this is to display an input box, requesting the user to enter a value.
For example, Listing 12.1 contains a simple procedure named CreatePresentations that displays an input box prompting users to enter the number of presentations they want to create. It then uses a For…Next loop to create the documents in PowerPoint.
1. Sub CreatePresentations() 2. Dim intPresentations As Integer 3. Dim i As Integer 4. intPresentations = InputBox _ ("Enter the number of presentations to create:", _ "Create Presentations") 5. For i = 1 To intPresentations 6. Presentations.Add 7. Next i 8. End Sub
Here's what happens in the CreatePresentations procedure in Listing 12.1:
CONTROL A For…Next LOOP WITH USER INPUT VIA A DIALOG BOX
An input box returns only a single value. Sometimes you need multiple values from the user. So, for those occasions when an input box won't suffice, you can easily get input from a dialog box to drive a For…Next loop. This book hasn't yet shown you how to create dialog boxes, but in this section you'll get a sneak preview by looking at a procedure named Create_Folders. You aren't expected to build and test this example; just read the code to get an idea of how it accepts user input and then employs that information in the loop.
This example procedure reduces the tedium of creating multiple folders with predictable names, such as when I had to create 31 folders, a folder for each chapter in this book.
Say that you're using a four-digit number to identify the project, the letter s for section, and a two-digit number to identify the section. So you'd end up with folders named 1234s01, 1234s02, 1234s03, and so on—simple enough to create manually, but tedious if you need more than a dozen or so.
In its simplest form, this dialog box would provide a text box for the number of folders to be created (though you could also use a drop-down list for this, or even a spinner control) and a text box for the project number. The following illustration is an example of how this dialog box might look.
You display a dialog box by using the Show method in a separate macro, perhaps using a Load statement first, like this:
Sub makefolders() Dialogs(wdDialogFileSaveAs).Show Load frmCreateFolders frmCreateFolders.Show End Sub
You might have noticed the Dialogs command in this code. It's quite useful, but we'll discuss it at the end of this sidebar. For now, our focus is on looping techniques.
I named the example dialog box frmCreateFolders. However, any valid VBA name will work. The first text box—identified with the Number Of Folders To Create label—is named txtFolders; the second text box is named txtProjectNumber.
The Cancel button here has an End statement attached to its Click event so that if the user clicks it, VBA ends the procedure:
Private Sub cmdCancel_Click() End End Sub
The OK button in the dialog box has the following code attached to its Click event:
1. Private Sub cmdOK_Click() 2. 3. Dim strMsg As String 4. Dim strFolder As String 5. Dim i As Integer 6. 7. frmCreateFolders.Hide 8. Unload frmCreateFolders 9. strMsg = "The Create_Folders procedure has created " _ & "the following folders: " & vbCr & vbCr 10. 11. For i = 1 To txtFolders.Value 12. strFolder = txtProjectNumber.Value & "p" & Format(i, "0#") 13. MkDir strFolder 14. strMsg = strMsg & " " & strFolder & vbCr 15. Next i 16. 17. MsgBox strMsg, vbOKOnly + vbInformation, _ "Create Folders" 18. 19. End Sub
Let's pause here a minute for a pep talk. You might read the preceding code and say, “Hey! I'll never be able to remember all this stuff about Format and Hide and vbCr and vbOKOnly.” Don't pout. Nobody memorizes all the variations of the Format command, or all the vb constants like vbCr. Remember, there are tons of sample code examples on the Internet and in books like this one. What's more, the VBA Editor itself displays lists of constants and object members as you type in a line of code. (Look up “Auto List Members” in this book's index. Or search the VBA Editor's Help index to locate online resources.)
Now back to our regular programming. Notice that the Value properties of the two text boxes are used in this loop. The value in txtFolders specifies the loop's number of iterations. The txtProjectNumber specifies the first part of the name for each newly created folder.
The cmdOK_Click procedure runs when the user clicks the OK button in the dialog box:
This procedure creates a set of new subfolders within whatever is the current folder, without giving the user a choice of location. Chances are you won't want to do this in real-life situations. You might want to change a folder to a set location (so as to keep all the project files together), but more likely you'll want to let the user choose a suitable location—for example, by displaying a common dialog box, such as the Save As dialog box used by most Windows applications. These built-in dialog boxes can be very useful because everyone who uses Windows is familiar with them and because they contain quite a bit of functionality. You display, for example, the classic Windows SaveAs dialog box like this:
Dialogs(wdDialogFileSaveAs).Show
When the user closes this dialog box, whatever folder the user specifies becomes the current folder and the document is automatically saved. You can find out more about how to use common dialog boxes in Chapter 14, “Creating Simple Custom Dialog Boxes,” and also at this Microsoft web page:
http://msdn.microsoft.com/en-us/library/bb208857.aspx
I wanted you to be aware that common dialog boxes exist, but in this example, perhaps a more direct way of allowing the user to specify the path for the new directories would be to use the ChDir (change directory) command, like this:
Dim strDir As String
strDir = InputBox("Type the full path where you want new folders to be stored")
ChDir (strDir)
The For Each… Next loop, which is unique to the various versions of Visual Basic, including VBA, is similar to the For…Next loop. With For Each, however, the iterations are based on the number of objects in a collection, such as the Slides collection in a presentation or the Documents collection of Word documents. So, using For Each means that you, the programmer, don't necessarily know the number of loop iterations in advance, but VBA will know during execution because it will query an object's Count property.
For example, you can choose to take an action for each Slide object in a presentation. During design time while writing your macro you don't need to know how many slides are in the collection. (If there are none, nothing happens.)
The syntax for the For Each… Next statement is straightforward:
For Each object In collection [statements] [Exit For] [statements] Next [object]
VBA starts by evaluating the number of objects in the specified collection. It then executes the statements in the loop for the first of those objects. When it reaches the Next keyword, it loops back to the For Each line, reevaluates the number of objects, and performs further iterations as appropriate.
Here's an example: The Documents collection contains the open documents in Word. So you could create a straightforward procedure to close all the open documents by using a For Each… Next loop like this:
Dim Doc As Document For Each Doc in Documents Doc.Close SaveChanges:=wdSaveChanges Next
VBA closes each open document in turn by using the Close method. The statement uses the wdSaveChanges constant for the SaveChanges argument to specify that any unsaved changes in the document be saved when the document is closed. As long as there are open documents in the Documents collection, VBA repeats the loop, so it closes all open documents and then terminates the procedure.
This example provides a straightforward illustration of how a For Each… Next loop works, but you probably wouldn't want to use the example in practice. Instead, you'd probably use the Close method with the Documents collection (this collection contains all the open documents) to close all the open documents. It's a simpler approach. However, you might use a For Each… Next loop to check each document for certain characteristics before closing it.
As you saw earlier in this chapter when looking at the syntax for For statements, you can use one or more Exit For statements to exit a For…loop if a certain condition is met. Exit For statements are optional and are seldom necessary. If you find yourself needing to use Exit For statements in all your procedures, there's probably something wrong with the way you're constructing your loops. That said, you may sometimes find Exit For statements useful—for example, to respond to an error that happens within a loop or if the user chooses to cancel a procedure.
On those occasions when you do need Exit For statements to exit a loop early, you'll typically use them with straightforward conditions. For example, in Word, if you wanted to close open windows until you reached a certain document that you knew to be open, you could use an Exit For statement like this:
Dim Doc As Document
For Each Doc in Documents
If Doc.Name = "Document1" Then Exit For
Doc.Close
Next Doc
This For Each… Next statement checks the Name property of the document to see if it's Document1; if it is, the Exit For statement causes VBA to exit the loop. Otherwise, VBA closes the document and returns to the start of the loop.
USE MULTIPLE Exit For STATEMENTS IF YOU WISH
You can also use multiple Exit For statements if you need to. For example, you might need to check two or more conditions during the actions performed in the loop.
Do loops give you more flexibility than For…loops in that you can test for conditions and direct the flow of the procedure accordingly. VBA includes several types of Do loops:
These loops break down into two categories:
The difference between the two types of loop in each category is that each While loop repeats itself while a condition is True (until the condition becomes False), whereas each Until loop repeats itself until a condition becomes True (while the condition remains False).
This means that you can get by to some extent using only the While loops or only the Until loops—you'll just need to set up some of your conditions the other way around. For example, you could use a Do While… Loop loop with a condition of x < 100 or a Do Until… Loop loop with a condition of x = 100 to achieve the same effect. Put another way: loop while x is less than 100 is equivalent to loop until x = 100—as long as you start looping below 100.
The following sections describe all the different kinds of Do loops so that you can know when to use each.
In a Do While… Loop loop, you specify a condition that has to remain True for the actions (statements) inside the loop to be executed. If the condition isn't True, the actions aren't executed and the loop ends. When a loop ends, the code below the loop block then executes.
For example, you might want to search a document for an instance of a particular word or phrase and take action after you find it. Figure 12.1 shows a Do While… Loop loop.
The syntax for the Do While… Loop loop is straightforward:
Do While condition [statements] [Exit Do] [statements] Loop
While the condition is met (Do While the condition remains True), the statements in the loop are executed. The Loop keyword returns execution to the Do While line, which is then reevaluated. If the condition is still True, the loop continues—it iterates again.
However, if the condition is False, execution jumps to the code below the loop block, starting with the statement on the line after the Loop keyword.
You can use one or more optional Exit Do statements if you want to exit the loop without waiting until the condition turns False.
Say you wanted to construct a glossary from a lengthy Word document that highlights the main terms by italicizing them. These terms are located in the body text as well as within bulleted or numbered lists. However, you want to avoid picking up italicized terms used in other elements of the document, such as headings or captions. In this situation, body text is in the Times New Roman font, but the captions and headlines are in other fonts.
You could command Word to search for Times New Roman text with the italic attribute. If Word found instances of the text, it would take the appropriate actions, such as selecting the sentence containing the term, together with the next sentence (or the rest of the paragraph), and copying it to the end of another document. Then it would continue the search, performing the loop until it no longer found instances of italic Times New Roman text.
Listing 12.2 shows an example of how such a procedure might be constructed with a Do While… Loop structure. This listing includes a number of commands that you haven't learned about yet, but you should easily be able to see how the loop works.
1. Sub GenerateGlossary() 2. 3. Dim strSource As String 4. Dim strDestination As String 5. Dim strGlossaryName As String 6. 7. strSource = ActiveWindow.Caption 8. strGlossaryName = InputBox _ ("Enter the name for the glossary document.", _ "Create Glossary") 9. If strGlossaryName = "" Then End 10. 11. Documents.Add 12. ActiveDocument.SaveAs FileName:=strGlossaryName, _ FileFormat:=wdFormatDocument 13. strDestination = ActiveWindow.Caption
14. Windows(strSource).Activate 15. 16. Selection.HomeKey Unit:=wdStory 17. Selection.Find.ClearFormatting 18. Selection.Find.Font.Italic = True 19. Selection.Find.Font.Name = "Times New Roman" 20. Selection.Find.Text = "" 21. Selection.Find.Execute 22. 23. Do While Selection.Find.Found 24. Selection.Copy 25. Selection.MoveRight Unit:=wdCharacter, _ Count:=1, Extend:=wdMove 26. Windows(strDestination).Activate 27. Selection.EndKey Unit:=wdStory 28. Selection.Paste 29. Selection.TypeParagraph 30. Windows(strSource).Activate 31. Selection.Find.Execute 32. Loop 33. 34. Windows(strDestination).Activate 35. ActiveDocument.Save 36. ActiveDocument.Close 37. 38. End Sub
The GenerateGlossary procedure in Listing 12.2 copies italic items in the Times New Roman font from the current document and inserts them in a new document that it creates and saves. Here's what happens:
A Do… Loop While block is similar to a Do While… Loop, except that in the Do… Loop While loop, the statements contained within the loop are executed at least once.
Whether the condition is True or False, the loop executes at least the first time through because the condition isn't tested until the end of the loop block.
If the condition is True, the loop continues to run until the condition becomes False. Figure 12.2 shows a Do… Loop While loop.
The Do While… Loop block described earlier probably made immediate sense to you, but this Do… Loop While block may seem odd. You're going to execute the contained statements before checking the condition?
But you'll find that Do… Loop While loops can be very useful, although they lend themselves to different situations than Do While… Loop loops.
Consider the lottery example from the beginning of the chapter. In that situation, you execute the action before you check the condition that controls the loop. First you buy a lottery ticket, and then you check to see if you've won. If you haven't won, or you've won only a small sum, you loop back and buy more tickets for the next lottery. (Actually, this is logically a Do… Loop Until loop rather than a Do… Loop While loop because you continue the loop while the condition is False; when you win a suitably large amount, the condition becomes True.)
Likewise, in programming it's not uncommon to take an action and then check whether you need to repeat it. For example, you might want to apply special formatting to the current paragraph and then check to see if other paragraphs need the same treatment.
The syntax for a Do… Loop While loop is as follows:
Do [statements] [Exit Do] [statements] Loop While condition
VBA performs the statements included in the loop, after which the Loop While line evaluates the condition. If it's True, VBA returns execution to the Do line and the loop continues to execute; if it's False, execution continues at the line after the Loop While line.
As an example of a Do… Loop While loop, consider this crude password checker that you could use to prevent someone from executing a macro without supplying the correct password:
Dim varPassword As Variant
VarPassword = "corinth"
Do
varPassword = InputBox _
("Enter the password to start the procedure:", _
"Check Password 1.0")
Loop While varPassword <> "CorrectPassword"
Here the Do… Loop While loop first displays an input box for the user to enter the password. The Loop While line compares the value from the input box, stored in varPassword, against the correct password (here, CorrectPassword). If the two aren't equal (varPassword <> "CorrectPassword"), the loop continues, displaying the input box again.
This loop is just an example—you wouldn't want to use it as it is in real life. Here's why: Choosing the Cancel button in an input box causes it to return a blank string, which also doesn't match the correct password, causing the loop to run again. The security is perfect; the problem is that the only way to end the loop is for users to supply the correct password. If they're unable to do so, they will see the input box again and again. There's no way out of the loop. This is called an endless loop and it's really bad programming. The user can get hopelessly trapped with the code repeating endlessly (in this case if they can't remember the password). Such loop stalls are also called infinite loops. More on these at the end of this chapter.
You should build a more friendly password-checking procedure. You might specify a number of incorrect password guesses that the user could enter (perhaps three) and then if they still haven't gotten it right, make the procedure terminate itself. Or you could simply use an End statement to terminate the procedure if the user entered a blank string, like this:
Do
varPassword = InputBox _
("Enter the password to start the procedure:", _
"Check Password 1.0")
If varPassword = "" Then End
Loop While varPassword <> "CorrectPassword"
A Do Until… Loop loop is similar to a Do While… Loop loop. The difference is how the condition works. In a Do Until… Loop loop, the loop runs while the condition is False and stops running when it's True. So this is the opposite of the way that the condition works in a Do While… Loop loop.
Figure 12.3 shows a Do Until… Loop loop.
Do Until…Loop BLOCKS EXECUTE UNTIL A CONDITION BECOMES FALSE
Note that Do Until… Loop loops are useful if you prefer to work with a condition that's True and keep it looping until the condition becomes False. Otherwise, you can achieve the same effects using Do While… Loop loops and inverting the condition. In other words, these two approaches to looping are functionally the same; it's just a matter of how you want to manage the condition. It's the difference between “sweep the porch until it's clean” versus “sweep the porch while it's still dirty.” Same idea, expressed differently.
The syntax for Do Until… Loop loops is as follows:
Do Until condition statements [Exit Do] [statements] Loop
When VBA enters the loop, it checks the condition. If the condition is False, VBA executes the statements in the loop, encounters the Loop keyword, and loops back to the beginning of the loop, reevaluating the condition as it goes. If the condition is True, VBA terminates the loop and continues execution at the statement after the Loop line.
For example, here's the lottery example once again, but now employing a Do…Until loop in Listing 12.3.
1. Sub Lottery_1() 2. Dim intWin As Integer 3. Do Until intWin > 2000 4. intWin = Rnd * 2100 5. MsgBox intWin, , "Lottery" 6. Loop 7. End Sub
Here's how Listing 12.3 works:
Listing 12.4 shows a more useful example of a Do Until… Loop loop in Word.
1. Sub FindNextHeading() 2. Do Until Left(Selection.Paragraphs(1).Style, 7) = "Heading" 3. Selection.MoveDown Unit:=wdParagraph, _ Count:=1, Extend:=wdMove 4. Loop 5. End Sub
Listing 12.4 contains a short procedure that moves the insertion point to the next heading in the active document in Word. Here's how it works:
The Do… Loop Until loop is similar to the Do Until… Loop structure except that in the Do… Loop Until loop, the statements contained within the loop block are executed at least once, whether the condition is True or False. If the condition is False, the loop continues to run until the condition becomes True. Figure 12.4 shows a Do… Loop Until loop.
The syntax for Do… Loop Until loops is as follows:
Do [statements] [Exit Do] [statements] Loop Until condition
VBA enters the loop at the Do line and executes the statements in the loop. When it encounters the Loop Until line, it checks the condition. If the condition is False, VBA loops back to the Do line and again executes the statements. If the condition is True, VBA terminates the loop and continues execution at the line after the Loop Until line.
As an example, say you want to repeatedly display an input box that adds new worksheets to a workbook until the user clicks the Cancel button or enters an empty string in the text box. You could use code like that shown in Listing 12.5.
1. Sub Create_Worksheets()
2. Dim strNewSheet As String
3. Do
4. strNewSheet = InputBox _
("Enter the name for the new worksheet " _
& "(31 characters max.):", "Add Worksheets") 5. If strNewSheet <> "" Then 6. ActiveWorkbook.Worksheets.Add 7. ActiveSheet.Name = strNewSheet 8. End If 9. Loop Until strNewSheet = "" 10. End Sub
Here's what happens in the Create_Worksheets procedure:
As with an Exit For statement in a For…loop, you can use an Exit Do statement to exit a Do loop without executing the statements below the Exit line. The Exit Do statement is optional, and you'll probably seldom want to use Exit Do statements in your loops—at least if the loops are properly designed.
When you do need an Exit Do statement, you'll generally use it with its own condition. The example shown in Listing 12.6 makes the lottery a little more interesting by adding an If condition with an Exit Do statement to take effect if the win is less than $500.
The procedure in Listing 12.6 works in the same way as the example in Listing 12.3 except that line 5 introduces a new If condition. If the variable intWin is less than 500, the statements in lines 6 and 7 run. Line 6 displays a message box announcing that the player has been disqualified from the lottery, and line 7 exits the Do loop.
Some programmers consider using an Exit Do statement to exit a Do loop a tactic of last resort, or at least clumsy programming. Others disagree. Many reckon that it's always acceptable to use an Exit Do statement to respond to an error or to the user clicking a cancel button.
VBA executes Exit Do statements with no problem, so it's there if you want to use it. However, you can often rewrite your code to avoid using an Exit Do statement.
For example, a condition that you check in the middle of the loop to decide whether to exit the loop can often be built into the main condition of the loop by using an operator such as And, Or, or Not, as shown in Listing 12.7:
1. Sub Lottery_3()
2.
3. Dim intWin As Integer
4.
5. Do
6. intWin = Rnd * 2100
7. MsgBox intWin, , "Lottery"
8. Loop Until intWin > 2000 Or intWin < 500
9.
10.
11. If intWin < 500 Then
12. MsgBox "Tough luck. You have been disqualified.", _
13. vbOKOnly + vbCritical, "Lottery"
14. End If
15.
16. End Sub
Listing 12.7 is a revision of the example in Listing 12.6. Listing 12.7 shows you how to use the Or operator to specify two conditions for the loop to iterate. In this way, you can omit the Exit Do command entirely.
In line 8 of Listing 12.7, we are saying that the loop should end if the variable is greater than 2000 Or less than 500. This makes it somewhat clearer what the loop is doing.
We must also make two other changes. First, we have to move the condition test from the top of the loop to the bottom. The Do Until command in Listing 12.6 must be changed to the Loop Until command in Listing 12.7. If we leave the condition test at the top of the loop, the condition will always prevent the loop from executing. This is because the intWin variable will always hold zero when this loop first executes. So we move the condition test to the bottom of the loop, allowing the variable to be assigned some value in line 6.
The final change we need to make is to move the If…Then block down to the bottom of the procedure.
If the code is simple like this example, you might be better off rewriting it to employ an operator. But if the code is complex and lengthy, there's no good reason to force yourself to use operators when an Exit Do statement will do the trick instead.
In addition to the For…Next loop, the For Each… Next loop, and the four flavors of Do loops examined so far in this chapter, VBA includes the While… Wend loop. While… Wend is VBA's version of the While… Wend looping structure used by earlier programming languages, such as the WordBasic programming language used with versions of Word up to and including Word 95. VBA includes While… Wend more for compatibility with those earlier versions than as a recommended technique. But you can use it if you choose to. The various Do loops have replaced While… Wend, but While… Wend still works fine.
The syntax of a While… Wend loop is as follows:
While condition [statements] Wend
While the condition is True, VBA executes the statements in the loop. When it reaches the Wend keyword (which is a contraction of While End), it returns to the While statement and evaluates the condition again. When the condition evaluates as False, the statements in the loop are no longer executed and execution moves to the statement after the Wend statement.
The following statements create a simple While… Wend loop for Word:
While Documents.Count < 10 Documents.Add Wend
While the number of documents in the Documents collection (measured here by the Count property of the Documents collection) is smaller than 10, the loop runs. Each time through, the Documents.Add statement in the second line creates a new document based on the Normal template (because no other template is specified). After the new document is created, the Wend statement in the third line returns execution to the first line, where the While condition is evaluated again.
AVOID BRANCHING INTO THE MIDDLE OF A While…Wend LOOP
If you do use a While… Wend loop, make sure the only way to enter the loop is by passing through the gate of the While condition. Branching into the middle of a While… Wend loop (for example, by using a label and a GoTo statement) can cause errors.
You can nest one or more loops within another loop to create the pattern of repetition you need: You can nest one For…loop inside another For…loop, a For…loop inside a Do loop, a Do loop inside a For…loop, or a Do loop inside a Do loop.
VBA PERMITS UP TO 16 LEVELS OF NESTING, BUT WHO COULD UNDERSTAND SUCH COMPLEXITY?
You can nest up to 16 levels of loops in VBA, but you'll be hard-pressed to comprehend even half that number of levels as you read over your code. If you find your code becoming this complicated, consider whether you can take a less tortuous approach to solve the problem.
For example, if you need to create a number of folders, each of which contains a number of subfolders, you could use a variation of the Create_Folders procedure you looked at earlier in the chapter. But such a task cries out for nesting.
The dialog box for the procedure will need another text box to contain the number of subfolders to create within each folder. The new dialog box is named frmCreateFoldersAndSubFolders and the text box for the number of subfolders is named txtHowManySubFolders.
Figure 12.5 shows the dialog box.
Listing 12.8 shows the code triggered by the Click event on the cmdOK button of the form.
1. Private Sub cmdOK_Click() 2. 3. Dim strStartingFolder As String
4. Dim strFolderName As String 5. Dim strSubfolderName As String 6. Dim intSubfolder As Integer 7. Dim intLoopCounter As Integer 8. 9. frmCreateFoldersAndSubfolders.Hide 10. Unload frmCreateFoldersAndSubfolders 11. 12. strStartingFolder = CurDir 13. 14. For intLoopCounter = 1 To txtHowManyFolders.Value 15. strFolderName = txtProjectNumber.Value & "s" & _ Format(intLoopCounter, "0#") 16. MkDir strFolderName 17. ChDir strFolderName 18. For intSubfolder = 1 To txtHowManySubfolders.Value 19. strSubfolderName = "Subsection" & intSubfolder 20. MkDir strSubfolderName 21. Next intSubfolder 22. ChDir strStartingFolder 23. Next intLoopCounter 24. 25. End Sub
Here's what the code in Listing 12.8 does:
USE THE COUNTER VARIABLE WITH Next WHEN NESTING For…LOOPS
Using counter variables with the Next command is optional (in Listing 12.8, the counter variables are named intLoopCounter and intSubfolder). You could simply use Next by itself and VBA will figure out what you mean. But when nesting For…loops, it's a good idea to include a counter variable to make it easier to see which loop is ending with the Next command (in other words, use Next intLoopCounter, for example, rather than just the shorthand version Next). Using a counter variable makes your procedures much easier to read and may prevent unpleasant surprises (bugs). Your nested loops must end in the exact reverse order of their starting, and the counters need to match.
If you create an infinite (aka endless) loop in a procedure, it will happily run forever, unless the user presses Ctrl+Break, presses Ctrl+Alt+Del to use the Task Manager to shut down the frozen application, restarts the computer, or pulls the plug.
For example, one type of loop you haven't yet encountered is the Do… Loop. As you can see in the example in Listing 12.9, without a condition attached to it, this structure is an infinite loop. There's no condition that can stop the looping.
In Listing 12.9, line 2 declares the variable x, and line 3 assigns it the value 1. Line 4 begins the Do loop, which displays a status-bar message and increases the value of x by 1. The effect of this loop is to display a message and an ever-increasing number on the status bar until you press Ctrl+Break to stop the procedure or until the value overflows the variable's maximum value. This is all thoroughly pointless (except perhaps as a way to burn in a new computer) and is perhaps a good reason not to use the Do… Loop structure—at least not without a condition attached to one end of it.
No matter what type of loop you use, to avoid creating an infinite loop, you need to make sure the condition that will terminate the loop can be satisfied at some point. For example, for an editing or cleanup procedure, you'll often want to perform an action until the end of the document is reached and then stop. Or you'll want to include some form of counting mechanism to make sure a Do loop doesn't exceed a certain number of iterations.
Understand when to use loops. Loops come in very handy when you need to perform a repetitive task, such as searching through a document for a particular word.
Master It What is the alternative to looping if you are carrying out repetitive tasks in a macro?
Use For…loops for fixed repetitions. For…loops are the most common loop structures in programming. You specify the number of iterations the loop must make, and the loop is exited when that number is reached.
Master It Write a For…Next loop that counts up to 100, but use the Step command to increment by twos.
Use Do… loops for variable numbers of repetitions. A Do… loop iterates until or while a condition exists, then exits from the loop when the condition no longer exists.
Master It There are two categories of Do… loops. Do While… Loop and Do Until… Loop loops test a condition before performing any action. What is the other category?
Nest one loop within another loop. You can put loops inside other loops.
Master It Think of a programming task where nested loops would be useful.
Avoid infinite loops. An infinite (or endless) loop causes your macro to continue execution indefinitely—as if the macro had stopped responding and was “frozen.”
Master It How do you avoid creating an infinite loop?
3.129.11.20