Creating LoopsIn his classic book The Wealth of Nations, economist Adam Smith asked how much it would cost to make a single straight pin compared with how much it would cost to make 10,000 straight pins. The cost of one pin is almost as great as the cost of all 10,000 pins. Similarly, writing a macro that runs once is almost as much work as writing a macro that runs thousands of times in a loop. Loop Through a Collection Using a For Each Loop Excel allows you to protect a worksheet so that nobody can change anything in cells that aren't specifically unlocked. You must, however, protect each sheet individually. Suppose that you have a workbook containing budgets for ten different departments and that you want to protect all the worksheets. The Chapter07 workbook includes a macro named ProtectSheets. Here's what it looks like: Sub ProtectSheets() Dim mySheet As Worksheet Set mySheet = Worksheets(1) mySheet.Select mySheet.Protect "Password", True, True, True End Sub This macro assigns a reference to the first worksheet to the mySheet variable, selects that sheet, and then protects it. (Selecting the sheet really isn't necessary, but it makes it easier to see what the macro is doing.) Now see how you can convert this macro to protect all the worksheets in the workbook.
Looping through a collection is as easy as assigning a single value to an object. Just replace Set with For Each, specify the collection, and add a Next statement.
Loop with a Counter Using a For Loop Sometimes you want to perform actions repeatedly but can't use a For Each loop. For example, a For Each loop can work through only a single collection. If you want to compare two parallel collections-such as two ranges-you can't use a For Each loop. In that situation, Visual Basic has another, more generalized way to loop: a For loop. The Compare worksheet in the Chapter07 workbook contains two named ranges. The one on the left is named Old, and the one on the right is named New. You can think of these as being an original forecast and a revised forecast. The cells in the Old range contain values. The cells in the New range contain a formula that will calculate a random number each time you press F9 to recalculate the workbook. (The formula in those cells is =ROUND(RAND()*50+100,0), which tells Excel to calculate a random number between 0 and 1, multiply it by 50, add 100, and round to the nearest whole number. Because the numbers are randomly generated, the ones you see will differ from the ones in the figure.) The Chapter07 module in the Visual Basic Editor contains a macro named CompareCells, which looks like this: Sub CompareCells() Dim i As Integer Calculate i = Range("New").Cells.Count If Range("New").Cells(i) > Range("Old").Cells(i) Then Range("New").Cells(i).Interior.Color = vbYellow Else Range("New").Cells(i).Interior.Color = vbCyan End If End Sub The macro first executes the Calculate method, which is like pressing the F9 function key to calculate new values for all the cells in the New range. Then the macro compares only the last cell in the New range with the last cell in the Old range. If the New value is greater than the Old, the new value turns yellow; otherwise, it turns blue. The macro assigns the Count of cells in the range to the variable i. The macro uses that number several times, and i requires less typing than Range("New").Cells.Count.
Now see how you can convert this macro to compare all the cells at once.
In many cases, using a For Each loop is more convenient than using a For loop. However, a For loop is a more general tool in that you can always use a For loop to reproduce the behavior of a For Each loop. For example, here's how you could write the ProtectSheets macro without using For Each: Sub ProtectSheets() Dim mySheet As Worksheet Dim i As Integer For i = 1 to Worksheets.Count Set mySheet = Worksheets(i) mySheet.Select mySheet.Protect "Password", True, True, True Next I End Sub If you were going to be marooned on a desert island and could take only one of these two looping structures with you, you'd probably be better off choosing For. Fortunately, however, you don't have to make the choice. In the many cases where For Each loops can work, use them and be happy. In cases where you need a numeric counter, use For loops. Loop Indefinitely Using a Do Loop A For Each loop works through a collection. A For loop cycles through numbers from a starting point to an ending point. In some situations, however, neither of these options works. For example, Visual Basic has a function that tells you the names of files in a folder (or directory). The function is named Dir, after the old MS-DOS operating system command of the same name. The first time you use Dir, you give it an argument that tells which kind of files you want to look at. To retrieve the name of the first Excel workbook in the current directory, you use the statement myFile = Dir("*.xls"). To get the next file that matches the same pattern, you use Dir again, but without an argument. You must run Dir repeatedly because it returns only one file name at a time. When Visual Basic can't find another matching file, the Dir function returns an empty string. Suppose that you want to create a macro that retrieves the names of all the Excel files in the current folder. The list of files in the directory isn't a collection, so you can't use a For Each loop. You can't use a For loop either because you don't know how many files you'll get until you're finished. Fortunately, Visual Basic has one more way of controlling a loop: a Do loop. The ListFiles macro in the Chapter07 workbook retrieves the first two Excel files from the current directory and puts their names into the first two cells in the first column of the active worksheet. Here's the original macro: Sub ListFiles() Dim myRow As Integer Dim myFile As String myRow = 1 myFile = Dir("*.xls") Cells(myRow, 1) = myFile myRow = myRow + 1 myFile = Dir Cells(myRow, 1) = myFile End Sub Aside from the variable declaration statements, this macro consists of two groups of three statements. In each group, the macro assigns a row number to myRow, retrieves a file name using the Dir function, and then puts the file name into the appropriate cell. The first time the macro uses Dir, it specifies the pattern to match. The next time, the macro simply uses Dir without an argument to retrieve the next matching file. Now see how you can convert this macro to loop until it has found all the files in the folder.
A Do loop is very flexible, but it's also a little bit dangerous because you have to be sure you provide a way for the loop to end. For example, if you forgot to add the statement to retrieve a new file name, or if you had included the argument to the Dir function inside the loop (so that Dir would keep returning the first file name over and over), you'd have what is called an infinite loop.
| |||||||||||||||||||||||||||||||||