Loops: further advanced examples Looping using the Cells property

Loops: further advanced examples
Looping using the Cells property
A common way to use the For loop is with the Cells property. Consider the worksheet illustrated; the
example code loops through the first five cells in column B one by one and doubles the value.
Sub CellsDemo()
Dim r As Integer
For r = 1 To 5
Cells(r, 2).Value = Cells(r, 2).Value * 2
Next r
End Sub
You may recall that the expression Cells(1,2) would refer to the cell in row 1 column 2, i.e., B2.
The code uses a variable in the Cells property for the row number, and explicitly sets the column
number to 2. The For loop initially sets this variable to 1 and subsequently increases it by one with
each repetition. Therefore Cells(r,2) in the loop references the first five row numbers, leaving the
column number unchanged. The effect is that the code in the body of the loop is repeated going down
column B.
Example
The following code takes a string from an input box and looks for it in the first column of the
worksheet. If it finds the string it selects the first instance of it. Use the code with worksheet named
Rates.
Sub findCurrency()
Dim r As Long, numrows As Long
Dim currname As String
Range("A1").Select
numrows = Selection.CurrentRegion.Rows.Count
currname = Trim(InputBox("Which Currency?"))
For r = 1 To numrows
If Cells(r, 1) = currname Then
Cells(r, 1).Select
Exit For
End If
Next
Trim function is used to
discard spaces from
beginning and end of user
input
If r = numrows + 1 Then
MsgBox "Not found"
Range("A1").Select
End If
End Sub
1
The For loop uses a variable called r to control the row number. In the body of the loop the worksheet’s
Cells property is used. The two numbers in the Cells property reference the row number and the column
number respectively. Note that the column number is always one. The row number is the variable r
which is incremented by 1 with every iteration of the For loop. Effectively the macro is referencing
every cell in the first column in turn.
If the value being searched is found, it is selected and there is no need to continue searching the rest of
the column. Hence the Exit For statement. The command Exit is usually seen with Exit Sub, but can be
used to exit a loop, so, Exit For.
Loops within loops
A loop can contain another loop; this is known as a ‘nested’ loop. Nested loops can be complex and
quite difficult to understand; however here’s an easy example. The program finds every instance of a
value (here, ‘Sean’, case insensitive) in every worksheet.
Sub FindSean()
Dim w As Worksheet
Dim r As Range
For Each w In Worksheets
For Each r In w.UsedRange
If Not IsError(r) Then 'necessary in case a cell has an #ERROR
If UCase(r) = "SEAN" Then
r.Interior.Color = vbYellow
Debug.Print "'sean\SEAN' found in " & w.name & "!" & r.Address
End If
End If
Next r
Next w
End Sub
The essential concept to grasp is that when you have a loop within a loop, the inner loop runs to
completion before passing back control to the outer loop. In the example, the outer loop is
For Each w In Worksheets
and the inner loop is
For Each r In w.UsedRange
The code would take the first worksheet in the file and instantiate it to the variable w. The code then
checks every cell (referenced by the variable r) in the used range to see if it contains ‘Sean’ – if so, the
cell is coloured. When the used range is finished, the statement Next w instantiates the next worksheet
to the variable w, and its used range is in turn processed. And so on, until all the worksheets have been
processed.
2
Macros that Delete
Deleting Empty Rows
First, to grasp the basic idea, here is a macro that is basically good, but might not work correctly in
every situation (it may fail if there are empty rows at the top of the worksheet).
For this problem we need to start at the bottom of the data and work up. If we delete starting from the
top then, when a row is deleted, Excel would have to recalculate all the references below the deleted
row – but some of these will be empty and also need to be deleted so it’s a waste of time. Therefore the
program will be more efficient if it starts with the last row of the data and works upwards.
Sub DeleteEmptyRows_Naive()
Dim LastRowNum As Long
' Long in case there is more than 32767 rows
Dim i As Integer
LastRowNum = ActiveSheet.UsedRange.Rows.Count
Application.ScreenUpdating = False
For i = LastRowNum To 1 Step -1
If WorksheetFunction.CountA(Rows(i)) = 0 Then
Rows(i).Delete
End If
Next i
Application.ScreenUpdating = True
End Sub
If you set the ScreenUpdating property to False, a macro which needs a lot of screen updating runs
faster. Just remember to set the property back to True before the end.
Deleting worksheet data
Deleting unwanted rows of data seems like it should be quite a basic procedure, but actually is more
tricky. The problems go away if you take the approach to delete starting at the bottom of the range and
work up.
Consider the illustrated worksheet;
suppose you wish to delete rows
where the value in column B is ‘OR’
A working macro might look like
the following:
Sub Delete1()
Dim i As Integer, n As Integer, counter As Integer
Cells(1).Select
n = ActiveCell.CurrentRegion.Rows.Count
For i = n To 1 Step -1
If Cells(i, 2) = "OR" Then
Rows(i).Delete
counter = counter + 1
End If
Next i
MsgBox counter & " rows deleted"
End Sub
3
The macro whould work and avoid problems. If you changed the code to delete from top to bottom
(line 5 would be For i = 1 To n) you would find that macro fails to delete where the value appears
in consecutive rows, e.g, row 10 in the illustration. This is because Excel rebuilds row numbers
beneath the deleted row which causes a synchronisation problem with the variable i in the loop and
effectively ‘misses’ one.
The example above uses Cells(row,column) which is an absolute reference, so would only work if the
data to be deleted is in column B. Supposing you want a more general macro which would delete
similar data at any location on the spreadsheet, so using the Offset property of the ActiveCell would
replace Cells(Row,Column). The macro also works from top to bottom which is more familiar to most
people.
Sub Delete2()
Dim counter As Integer
' ensure correct starting position on worksheet, e.g.
Selection.CurrentRegion.Cells(1).Select
Do While Not IsEmpty(ActiveCell)
If ActiveCell.Offset(0, 1) <> "OR" Then
ActiveCell.Offset(1, 0).Select
Else
Range(ActiveCell, ActiveCell.End(xlToRight)).Delete
counter = counter + 1
End If
Loop
MsgBox counter & " rows deleted"
End Sub
The Do While loop uses the Then clause to check the cell contents and travel down the column if it is
not for deletion. The Else clause does the deletion and updates the counter; note that the Else clause
doesn’t include the line to go down one cell because the act of deleting automatically causes the row
below to becom the ‘active’ row. This nicely illustrates the problem posed by Excel rebuilding row
numbers.
Deleting Empty Rows (revisited)
If there are empty rows at the top of the worksheet you cannot merely rely on the count of the rows in
the used range. The Row property of a range can help with this.
Row property
The Row property of a range is useful to determine the spreadsheet row number of a cell.
ActiveCell.Row
gives the row number of the active cell.
In the case of a selection the Row property gives the row number of the first row in the selection.
Selection.Row
would give 4 in the illustration.
(don’t confuse this with the Rows property. Selection.Rows.Count would give 10 in the illustration –
the selection has 10 rows)
4
The next code determines blank rows in a worksheet and deletes them. There are two aspects to this
task: getting the row number of the last row, and creating a loop to delete them.
Getting the row number of the last row
You can use ActiveSheet.UsedRange.Rows.Count if there are no blank rows at the top of the
worksheet. However the program is more reliable if it caters for the possibility that there may be blank
rows at the top.
In the illustration there are three blank rows at the top.
ActiveSheet.UsedRange.Rows.Count
gives 23, but the row number we need to work out is 26, because of the three blank rows.
ActiveSheet.UsedRange.Row gives 4
So ActiveSheet.UsedRange.Row - 1 gives 3, the number we need to add to 23, giving 26 which is
the solution we are seeking. The expression is therefore:
ActiveSheet.UsedRange.Row - 1 + ActiveSheet.UsedRange.Rows.Count
The workings of the loop are as discussed in the first section on page 3.
Sub DeleteEmptyRows()
Dim LastRowNum As Long
Dim i As Integer
' Long in case there is more than 32767 rows
LastRowNum = ActiveSheet.UsedRange.Row - 1 + ActiveSheet.UsedRange.Rows.Count
Application.ScreenUpdating = False
For i = LastRowNum To 1 Step -1
If WorksheetFunction.CountA(Rows(i)) = 0 Then
Rows(i).Delete
End If
Next i
Application.ScreenUpdating = True
End Sub
Ref: Power Programming with VBA, John Walkenbach
The Column property
The Column property is the column-wise equivalent of Row.
ActiveCell.Column gives the column coordinate of the active cell
Selection.Column gives the column number of the first column of the selection.
5
Dealing with case sensitive strings (StrComp function)
The following code takes a string in an input box and finds it on the worksheet. The problem is that the
user may or may not type capital letters.
Sub findCurrency1()
Dim r As Long, numRows As Long, currName As String
Range("A1").Select
Selection.CurrentRegion.Select
currName = Trim(InputBox("Which Currency?"))
numRows = Selection.Rows.Count
Trim function is used to discard spaces
from beginning and end of user input
For r = 1 To numRows
If Cells(r, 1) = currName Then
Cells(r, 1).Select
Exit For
End If
Next
If r = numRows + 1 Then
MsgBox "Not found"
Range("A1").Select
End If
End Sub
It’s easy to forget that strings are case sensitive; if your worksheet data is a mixture of upper and lower
case then you can use VBA’s inbuilt StrComp function, which tests two strings for equality.
works by comparing two strings, and returns 0 if they are equal (and 1 or -1 if they are not equal).
For example, in the Immediate Window
? strcomp(“London”, “London”)
would return 0
? strcomp(“London”, “Leeds”)
would not return 0 as the two strings are not the same.
However,
? strcomp(“London”, “LONDON”)
would not return 0 either because of case sensitivity. To see if two strings are equal leaving case aside use
StrComp with a third optional argument set to vbTextCompare. For example,
? strcomp(“London”, “LONDON”, vbTextCompare)
would return 0 as the two strings are the same if you disregard the case
StrComp
So, in the following code, the expression Cells(r,1) is compared with the variable currname to see if they
mean the same.
Sub findCurrency2()
Dim r As Long, numrows As Long, currname As String
numrows = Activesheet.UsedRange.Rows.Count
currname = Trim(InputBox("Which currency?"))
Program modified to use UsedRange
For r = 1 To numrows
If StrComp(Cells(r, 1), currname, vbTextCompare) = 0 Then
Cells(r, 1).Select
Exit For
End If
Next
If r = numrows + 1 Then MsgBox "Not found"
End Sub
6
The Like operator
The Like operator is used for partial comparison of one string to another (* can be thought of as a ‘wild
card’ it stands for none, one or more characters). For example if you want cells that begin with ‘A’:
Sub LikeTest()
Dim r As Range
For Each r In Selection
If r Like "A*" Then 'case sensitive
Debug.Print r & " begins with 'A' in cell " & r.address
End If
Next r
End Sub
You could modify the fourth line to say
If r Like "A*" Or r Like "a*" Then
if you wanted to find cells that began with ‘A’ or ‘a’
Remember the * can also stand for zero characters so
Like “M*cDonald”
would match MacDonald and McDonald
The ? operator works like * but stands for one character only in the exact position.
For example
Like “AA?”
Would match
AAA
AAa
AAb
but not
AA
because there is not a third character in the last example.
7