Döngüler Ozg

13:46 0 Comments A+ a-

 To put it bluntly I very often avoid Loops, they are far too sloooow in many cases. A common mistake we all make when first learning VBA is to use Loops when we really shouldn't..Take the simple example below for instance. It Loops through a range and places the word "Blank" in each blank cell within a used range, i.e it assumes the last occupied cell is D500 

Sub WrongWay()

Dim Bcell As Range

   For Each Bcell In Range("A1:D500")

    If IsEmpty(Bcell) Then Bcell = "Blank"

   Next Bcell

End Sub

Now compare the above code to this one: 

Sub RightWay()

    If WorksheetFunction.CountA(Range("A1:D500")) = 0 Then

       MsgBox "All cells are empty", vbOKOnly, "OzGrid.com"

       Exit Sub

    End If

    On Error Resume Next

    Range("A1:D500").SpecialCells(xlCellTypeBlanks) = "Blank"

    On Error GoTo 0

End Sub

Not only is it much easier to type, but it will run much much quicker.

Next time you have a VBA task, don't rush in with a Loop. Step back and give some serious thought to Excels built in functions. Some of my favourites to use are: SpecialCells, AutoFilter, Find, AdvancedFilter and Subtotals to name but a few. Once you start to make use of these you will always think twice before using a Loop of any kind.

 Instead of a Loop, try inserting a Column and placing a formula in the required range that makes the check on the cells. Use a number for a Yes and text for a No, then use SpecialCells to do the rest. I can promise you there is nearly always a built in feature that will execute at least 100 times quicker than a Loop. You just need to step outside the box!

Here is another comparison to stress my point!:
Place the text "Find Me" into cell IV65536 and run this code: 

Sub NoLoop()

    If WorksheetFunction.CountIf(Cells, "Find Me") = 0 Then

       MsgBox "You didn't type 'Find Me'", vbOKOnly, "OzGrid.com"

       Exit Sub

    End If

    

    Cells.Find(What:="Find Me", After:=[A1], LookIn:=xlFormulas, _

          LookAt:=xlPart, SearchOrder:=xlByRows, _

           SearchDirection:=xlNext, MatchCase:=False).Activate

        

End Sub

Now if you have at least 5 minutes to spare, try this code that uses a Loop: 

Sub WithLoop()

Dim rCell As Range


   For Each rCell In Cells

    If rCell.Value = "Find Me" Then

        rCell.Activate

        Exit For

      End If

   Next rCell

End Sub

To stop the Loop, push Ctrl+Break or Esc. Now that has to at least make you try alternatives for Loops!

Now sometimes a Loop might be the only way or the best way (not too often though). If this is the case we should restrict our range to only the cells we need. The example below will change the font color of all negative value cells to yellow and the background to red for an entire Worksheet. Truth be known I would use conditional formatting! Before it performs the loop though it restricts the range to only numeric cells. 

Sub FastestLoop()

Dim rCcells As Range, rFcells As Range

Dim rAcells As Range, rLoopCells As Range


'Set variable to all used cells

Set rAcells = ActiveSheet.UsedRange

rAcells.Select

On Error Resume Next 'In case of no formula or constants.

'Set variable to all numeric constants

Set rCcells = rAcells.SpecialCells(xlCellTypeConstants, xlNumbers)

'Set variable to all numeric formulas

Set rFcells = rAcells.SpecialCells(xlCellTypeFormulas, xlNumbers)


    'Determine which type of numeric data (formulas, constants or none)

    If rCcells Is Nothing And rFcells Is Nothing Then

       MsgBox "You Worksheet contains no numbers"

       End

    ElseIf rCcells Is Nothing Then

       Set rAcells = rFcells 'formulas

    ElseIf rFcells Is Nothing Then

       Set rAcells = rCcells 'constants

    Else

       Set rAcells = Application.Union(rFcells, rCcells) 'Both

    End If

    On Error GoTo 0


    'Loop through needed cells only see if negative

    For Each rLoopCells In rAcells

        If rLoopCells.Value < 0 Then

            With rLoopCells

               .Interior.ColorIndex = 6

               .Font.ColorIndex = 3

            End With

       End If

    Next rLoopCells

End Sub


Here is another way to speed up a loop that makes use of Excel's COUNTIF function. The code could be modified to suit almost any situation very easily. This particular Procedure Bolds all instances of the "Cat" in Column "A" of the active sheet. 

Sub BoldCat()

Dim iLoop As Integer

Dim rNa As Range

Dim i As Integer


iLoop = WorksheetFunction.CountIf(Columns(1), "Cat")

Set rNa = Range("A1")


 For i = 1 To iLoop

  Set rNa = Columns(1).Find(What:="Cat", After:=rNa, _

             LookIn:=xlValues, LookAt:=xlWhole, _

             SearchOrder:=xlByRows, SearchDirection:=xlNext, _

             MatchCase:=True)

      rNa.Font.Bold=True       

    

 Next i


End Sub

What Loops Are Good For

The examples below here show what loops are good for, in this case it is looping through a text string and passing back the positions of / within the string to an array. The array (in this case) would result in {6,11,19,22}. Each number representing the position of each / 

Sub GetIt()

Dim i As Integer

Dim strText As String

Dim iPositions() As Integer

Dim iStart As Integer

Dim iLoop As Integer


strText = "Ihave/four/OfThese/In/Me"


'Find out how many "/" are within the string by subtracting _

 Len("Ihave/four/OfThese/In/Me") from Len("IhavefourOfTheseInMe") _

 This will result in four. We then take 1 because the first element _

 in an Array is always zero, unless told otherwise.

iLoop = Len(strText) - Len _

        (Application.Substitute(strText, "/", "")) - 1


'Tell the array how many elements it is to hold.

ReDim iPositions(iLoop) As Integer

iStart = 1


    For i = 0 To iLoop 'loop four times

        'Parse the position of the nth "/" starting from iStart.

        iPositions(i) = InStr(iStart, strText, "/")


        'Add one to the found position, for next InStr to start from.

        iStart = iPositions(i) + 1


        MsgBox "Number " & i + 1 & " '/' is in position " & _

        iPositions(i), vbInformation, "OzGrid.com"

    Next i


End Sub


Hide All But One Sheet

Loop through all sheets in a Workbook and hide all but Sheet1. Excel will not allow all sheets hidden. 

Sub HideAllButOneSheet()

'We must leave at least one Sheet visible

Dim wsSheet As Worksheet


    For Each wsSheet In Worksheets

       wsSheet.Visible = wsSheet.Name = "Sheet1"

    Next wsSheet


End Sub


Show All Sheets

Loop through all sheets in a Workbook and Show all Sheets. 

Sub ShowAllSheets()

Dim wsSheet As Worksheet


    For Each wsSheet In Worksheets

       wsSheet.Visible = True

    Next wsSheet


End Sub


Toggle Sheet Visibility

Loop through all sheets in a Workbook and toggle the visibility 

Sub ToogleSheetVisiblity()

'You must leave at least one Sheet visible

Dim wsSheet As Worksheet

On Error Resume Next 'If code tries to hide all Sheets

    For Each wsSheet In Worksheets

       wsSheet.Visible = Not wsSheet.Visible

    Next wsSheet

On Error GoTo 0 'One sheet will always be left visible

End Sub


Unprotect All Sheets

Loop through all sheets in a Workbook and Unprotect them. To protect simply replace Unprotect with Protect 

Sub ProtectAllSheets()

Dim wsSheet As Worksheet

On Error Resume Next

    For Each wsSheet In Worksheets

       wsSheet.Unprotect Password:="SecretWord"

    Next wsSheet

On Error GoTo 0

End Sub


Join the text of multiple cells

This code will display an InputBox that allows the user to select specific cells that will all be joined (Concatenated) in the first cell selected. 

Sub JoinCells()

Dim rCells As Range

Dim rRange As Range

Dim rStart As Range

Dim strStart As String

Dim iReply As Integer

On Error Resume Next

 

'Allow user to nominate cells to join

Set rCells = Application.InputBox _

  (Prompt:="Select the cells to join," _

  & "use Ctrl for non-contiguous cells.", _

  Title:="CONCATENATION OF CELLS", Type:=8)

  

 If rCells Is Nothing Then 'Cancelled or mistake

    iReply = MsgBox("Invalid selection!", _

             vbQuestion + vbRetryCancel)

     If iReply = vbCancel Then

        On Error GoTo 0

        Exit Sub

     Else

        Run "JoinCells" 'Try again

     End If

  End If

 

 'Set range variable to first cell

 Set rStart = rCells(1, 1)

 

 'Loop through cells chosen

 For Each rRange In rCells

    strStart = rRange 'parse cell content to a String

    rRange.Clear 'Clear contents of cell

    'Replace the original contents of first cell with "", then _

     join the text

    rStart = Trim(Replace(rStart, rStart, "") & " " _

                  & rStart & " " & strStart)

 Next rRange

  On Error GoTo 0

 

End Sub


'==============================================================

' This workbook was prepared and coded by Martin Green to help

' Excel users learn about VBA. Visit my web site for tips and

' tutorials on Microsoft Office and details of my Training and

' Consultancy services: http://www.fontstuff.com

'==============================================================


Sub Loop1()

' This loop runs until there is nothing in the next column

    Do

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub


Sub Loop2()

' This loop runs as long as there is something in the next column

    Do While IsEmpty(ActiveCell.Offset(0, 1)) = False

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Loop

End Sub


Sub Loop3()

' This loop runs as long as there is something in the next column

    Do While Not IsEmpty(ActiveCell.Offset(0, 1))

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Loop

End Sub


Sub Loop4()

' This loop runs as long as there is something in the next column

' It does not calculate an average if there is already something in the cell

    Do

    If IsEmpty(ActiveCell) Then

        ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    End If

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub


Sub Loop5()

' This loop runs as long as there is something in the next column

' It does not try to calculate an average if there is already something in the cell

' nor if there is no data to average (to avoid #DIV/0 errors).

    Do

    If IsEmpty(ActiveCell) Then

        If IsEmpty(ActiveCell.Offset(0, -1)) And IsEmpty(ActiveCell.Offset(0, -2)) Then

            ActiveCell.Value = ""

        Else

            ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

        End If

    End If

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub


Sub Loop6()

' This loop repeats for a fixed number of times determined by the number of rows in the range

    Dim i As Integer

    For i = 1 To Selection.CurrentRegion.Rows.Count - 1

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Next i

End Sub


Sub Loop7()

' This loop repeats a fixed number of times getting its reference from elsewhere

    Dim i As Integer

    Dim intRowCount As Integer

    intRowCount = Range("A1").CurrentRegion.Rows.Count - 1

    For i = 1 To intRowCount

    ActiveCell.FormulaR1C1 = "=Average(RC[-5],RC[-6])"

    ActiveCell.Offset(1, 0).Select

    Next i

End Sub


Sub Loop8()

' This loop does the calculating itself and writes the result into each cell

    Do

    ActiveCell.Value = WorksheetFunction.Average(ActiveCell.Offset(0, -1).Value, ActiveCell.Offset(0, -2).Value)

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub



Excel VBA Course Notes: 2. Using Loops
(File: VBA02-Loops.xls)

Why Loops?

The purpose of a loop is to get Excel to repeat a piece of code a certain number of times. How many times the code gets repeated can be specified as a fixed number (e.g. do this 10 times), or as a variable (e.g. do this for as many times as there are rows of data).

Loops can be constructed many different ways to suit different circumstances. Often the same result can be obtained in different ways to suit your personal preferences. These exercises demonstrate a selection of different ways to use loops.

There are two basic kinds of loops, both of which are demonstrated here: Do…Loop and For…Next loops. The code to be repeated is placed between the key words.

Open the workbook VBA02-Loops.xls and take a look at the four worksheets. Each contains two columns of numbers (columns A and B). The requirement is to calculate an average for the numbers in each row using a VBA macro. 

Now open the Visual Basic Editor (Alt+F11) and take a look at the code in Module1.  You will see a number of different macros. In the following exercises, first run the macro then come and read the code and figure out how it did what it did.

You can run the macros either from the Visual Basic Editor by placing your cursor in the macro and pressing the F5 key, or from Excel by opening the Macros dialog box (ALT+F8) choosing the macro to run and clicking Run. It is best to run these macros from Excel so you can watch them as they work.

Exercise 1: Do… Loop Until…

The object of this macro is to run down column C as far as is necessary putting a calculation in each cell as far as is necessary.

On Sheet1 select cell C2 and run the macro Loop1.

Here's the code:

Sub Loop1()

' This loop runs until there is nothing in the next column

    Do

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub

This macro places a formula into the active cell, and moves into the next cell down. It uses Loop Until to tell Excel to keep repeating the code until the cell in the adjacent column (column D) is empty. In other words, it will keep on repeating as long as there is something in column D.

Delete the data from cells C2:C20 and ready for the next exercise

Exercise 2: Do While… Loop

The object of this macro is to run down column C as far as is necessary putting a calculation in each cell as far as is necessary.

On Sheet1 select cell C2 and run the macro Loop2

Here's the code

Sub Loop2()

' This loop runs as long as there is something in the next column

    Do While IsEmpty(ActiveCell.Offset(0, 1)) = False

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Loop

End Sub

This macro does the same job as the last one using the same parameters but simply expressing them in a different way. Instead of repeating the code Until something occurs, it does something While something is the case. It uses Do While to tell Excel to keep repeating the code while there is something in the adjacent column as opposed to until there is nothing there. The function IsEmpty = False means "Is Not Empty".

Delete the data from cells C2:C20 and ready for the next exercise

Exercise 3: Do While Not… Loop

The object of this macro is to run down column C as far as is necessary putting a calculation in each cell as far as is necessary.

On Sheet1 select cell C2 and run the macro Loop3.

Here's the code:

Sub Loop3()

' This loop runs as long as there is something in the next column

    Do While Not IsEmpty(ActiveCell.Offset(0, 1))

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Loop

End Sub

This macro makes exactly the same decision as the last one but just expresses it in a different way. IsEmpty = False means the same as Not IsEmpty. Sometimes you can't say what you want to say one way so VBA often offers an alternative syntax.

Delete the data from cells C2:C20 and ready for the next exercise

Exercise 4: Including an IF statement

The object of this macro is as before, but without replacing any data that may already be there.

Move to Sheet2, select cell C2 and run the macro Loop4.

Here's the code:

Sub Loop4()

' This loop runs as long as there is something in the next column

' It does not calculate an average if there is already something in the cell

    Do

    If IsEmpty(ActiveCell) Then

        ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    End If

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub

The previous macros take no account of any possible contents that might already be in the cells into which it is placing the calculations. This macro uses an IF statement that tells Excel to write the calculation only if the cell is empty. This prevents any existing data from being overwritten. The line telling Excel to move to the next cell is outside the IF statement because it has to do that anyway.

Exercise 5: Avoiding Errors

This macro takes the IF statement a stage further, and doesn't try to calculate an average of cells that are empty. 

First, look at the problem. Move to Sheet3, select cell C2 and run the macro Loop4.

Note that because some of the pairs of cells in columns A and B are empty, the =AVERAGE function throws up a #DIV/0 error (the Average function adds the numbers in the cells then divides by the number of numbers - if there aren't any numbers it tries to divide by zero and you can't do that!).

Delete the contents of cells C2:C6 and C12:C20. Select cell C2 and run the macro Loop5.

Here's the code:

Sub Loop5()

' This loop runs as long as there is something in the next column

' It does not calculate an average if there is already something in the cell

' nor if there is no data to average (to avoid #DIV/0 errors).

    Do

    If IsEmpty(ActiveCell) Then

        If IsEmpty(ActiveCell.Offset(0, -1)) And IsEmpty(ActiveCell.Offset(0, -2)) Then

            ActiveCell.Value = ""

        Else

            ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

        End If

    End If

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub

Note that this time there are no error messages because Excel hasn't tried to calculate averages of numbers that aren't there.

In this macro there is a second IF statement inside the one that tells Excel to do something only if the cell is empty. This second IF statement gives excel a choice. Instead of a simple If there is an If and an Else. Here's how Excel reads its instructions… 

"If the cell has already got something in, go to the next cell. But if the cell is empty, look at the corresponding cells in columns A an B and if they are both empty, write nothing (""). Otherwise, write the formula in the cell. Then move on to the next cell."

Exercise 6: For… Next Loop

If you know, or can get VBE to find out, how many times to repeat a block of code you can use a For… Next loop.

Move to Sheet4, select cell C2 and run the macro Loop6.

Here's the code:

Sub Loop6()

' This loop repeats for a fixed number of times determined by the number of rows

' in the range

    Dim i As Integer

    For i = 1 To Selection.CurrentRegion.Rows.Count - 1

    ActiveCell.FormulaR1C1 = "=Average(RC[-1],RC[-2])"

    ActiveCell.Offset(1, 0).Select

    Next i

End Sub

This macro doesn't make use of an adjacent column of cells like the previous ones have done to know when to stop looping. Instead it counts the number of rows in the current range of data and uses the For… Next method to tell Excel to loop that number of times (minus one, because when VBA counts it starts at zero).

Exercise 7: Getting the Reference From Somewhere Else

Select cell G2 and run the macro Loop7.

Here's the code:

Sub Loop7()

' This loop repeats a fixed number of times getting its reference from elsewhere

    Dim i As Integer

    Dim intRowCount As Integer

    intRowCount = Range("A1").CurrentRegion.Rows.Count - 1

    For i = 1 To intRowCount

    ActiveCell.FormulaR1C1 = "=Average(RC[-5],RC[-6])"

    ActiveCell.Offset(1, 0).Select

    Next i

End Sub

You can get the reference for the number of loops from anywhere. This macro places a set of calculations in column G for a number of times dictated by the number of rows in the block of data starting with cell A1. The For… Next statement has been simplified a bit by first declaring a variable intRowCount and filling it with the appropriate information (how many rows in the block by A1). This variable gets used in the next line instead of a long line of code. This is just another example of doing the same job a different way.

If you wanted to construct a loop that always ran a block of code a fixed number of times, you could simply use an expression like:

    For i = 1 To 23

Exercise 8: About Doing Calculations…

All the previous exercises have placed a calculation into a worksheet cell by actually writing a regular Excel function into the cell (and leaving it there) just as if you had typed it yourself. The syntax for this is:

ActiveCell.FormulaR1C1 = “TYPE YOUR FUNCTION HERE”

These macros have been using:

ActiveCell.FormulaR1C1 = “=Average(RC[-5],RC[-6])”

Because this method actually places a function into the cell rather than a value, their results will change as the cells that they refer to change, just like regular functions – because they are regular functions. The calculating gets done in Excel because all that the macro did was to write the function.

If you prefer, you can get the macro to do the calculating and just write the result into the cell. VBA has its own set of functions, but unfortunately AVERAGE isn’t one of them. However, VBA does support many of the commoner Excel functions with its WorksheetFunction method.

On Sheet1 select cell C2 and run the macro Loop1

Take a look at the cells you just filled in. Each one contains a function, written by the macro. 

Now delete the contents from the cells C2:C20, select cell C2 and run the macro Loop8.

Here’s the code:

Sub Loop8()

    Do

    ActiveCell.Value = WorksheetFunction.Average(ActiveCell.Offset(0, -1).Value, _

         ActiveCell.Offset(0, -2).Value)

    ActiveCell.Offset(1, 0).Select

    Loop Until IsEmpty(ActiveCell.Offset(0, 1))

End Sub

Take a look at the cells you just filled in. This time there’s no function, just the value. All the calculating was done by the macro which then wrote the value into the cell.




Archived content. No warranty is made as to technical accuracy. Content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

C H A P T E R    4


Microsoft Office 97/Visual Basic Programmer's Guide


Microsoft Excel Objects



Contents 

Visual Basic supports a set of objects that correspond directly to elements in Microsoft Excel, most of which you're familiar with from the user interface. For example, the Workbook object represents a workbook, the Worksheet object represents a worksheet, and the Range object represents a range of cells on a worksheet. Every element in Microsoft Excel — each workbook, worksheet, chart, cell, and so on — can be represented by an object in Visual Basic. By creating procedures that control these objects, you automate tasks in Microsoft Excel. The object model in Microsoft Excel 97 contains extensive changes and modifications. To view a graphical representation of the Microsoft Excel 97 object model, see "Microsoft Excel Objects" in Help. For a list of changes and additions, see "Changes to the Microsoft Excel 97 Object Model" in Help.

Microsoft Excel 97 adds support for event-driven programming to Visual Basic. An event is any action recognized by a Microsoft Excel object. Several objects in Microsoft Excel recognize a predefined set of events; when you want an object to respond to an event in a particular way, you can write a Visual Basic event procedure for that event. 

How Do I Display Visual Basic Help for Microsoft Excel? 

To use Visual Basic Help for Microsoft Excel, you must click Custom during Setup and select the Online Help for Visual Basic check box for Microsoft Excel. Otherwise, Visual Basic Help won't be installed. If you've already installed Microsoft Excel, you can run Setup again to install Visual Basic Help. 

To see the contents and index of Visual Basic Help for Microsoft Excel, click Contents and Index on the Help menu in the Visual Basic Editor. On the Contents tab in the Help Topics dialog box, double-click "Microsoft Excel Visual Basic Reference," and then double-click "Shortcut to Microsoft Excel Visual Basic Reference." The Help Topics dialog box should reappear, displaying the contents and index for Visual Basic Help for Microsoft Excel. 

Working with the Application Object

Most properties of the Microsoft Excel Application object control the appearance of the application window or the global behavior of the application. For example, the value of the DisplayFormulaBar property is True if the formula bar is visible, and the value of the ScreenUpdating property is False if screen updating is turned off.

In addition, properties of the Application object provide access to objects lower in the object hierarchy, such as the Windows collection (representing all currently open windows) and the Workbooks collection (representing all currently open workbooks). You use these properties, sometimes called accessors, to move down the object hierarchy from the toplevel Application object to objects lower in the hierarchy (such as the Workbook, Worksheet, and Range objects). For more information about navigating through an object model, see Chapter 2, "Understanding Object Models." 

Some methods and properties that apply to the Application object also apply to objects lower in the object hierarchy. Using these properties or methods at the Application level usually changes all open workbooks or sheets. For example, the Calculate method applies to the Application, Workbook, and Worksheet objects. Using Application.Calculate recalculates all worksheets in all open workbooks, whereas using this method on the Workbook or Worksheet object provides greater control.

Working with the Workbook Object

When you open or save a file in Microsoft Excel, you're actually opening and saving a workbook. In Visual Basic, the methods for manipulating files are methods of the Workbook object or the Workbooks collection. 

Opening Workbooks

When you open a workbook, you use the Open method. The Open method always applies to the Workbooks collection, which you return using the Workbooks property. The following code opens the file Book1.xls (in the current folder) and then displays the value that's in cell A1 on the first worksheet in the workbook. 

Sub OpenBook1()

    Set myBook = Workbooks.Open(Filename:="BOOK1.XLS")

    MsgBox myBook.Worksheets(1).Range("A1").Value

End Sub

 

Notice that the return value of the Open method is a Workbook object that refers to the workbook that was just opened. 

The file name in this example doesn't contain a path; therefore, the file is assumed to be in the current folder. This is guaranteed to cause a runtime error, because as soon as the user changes the current folder, Visual Basic can no longer find the file.

There are two relatively safe places to store a workbook you want to open programmatically. One place is the folder that contains the executable file for Microsoft Excel. The other place is the Library folder, which is created automatically during setup; this folder is one level down from the folder that contains the executable file.

If you want to open a workbook that's saved in the folder that contains the executable file, you can use the Path property to return a string that specifies the folder. The PathSeparator property returns the correct separator character for the current file system (for example, a backslash (\) for MSDOS®/Windows® FAT, or a colon (:) for the Macintosh®). The following example shows filesystemindependent code you can use to open Book1.xls, assuming that Book1.xls is saved in the folder that contains the executable file. 

Sub OpenBook1()

    EXEPath = Application.Path & Application.PathSeparator

    fName = EXEPath & "BOOK1.XLS"

    Set myBook = Workbooks.Open(Filename:=fName)

    MsgBox myBook.Worksheets(1).Range("A1").Value

End Sub



The other relatively safe place to store a workbook is in the Library folder. You can use the LibraryPath property instead of the Path property to return a string that specifies the Library folder. The following code shows how you would alter the preceding example to use the LibraryPath property. 

Sub OpenBook1()

    LibPath = Application.LibraryPath & Application.PathSeparator

    fName = LibPath & "BOOK1.XLS"

    Set myBook = Workbooks.Open(Filename:=fName)

    MsgBox myBook.Worksheets(1).Range("A1").Value

End Sub

 

Instead of hardcoding a file name with the Open method, you may want to give the user the option of selecting a file to open. The GetOpenFilename method displays the standard Open dialog box, but the method returns a string instead of opening a file. The string contains the fully qualified path and file name. The following example demonstrates the GetOpenFilename method by displaying the return value in a message box and then opening the file. 

Sub DemoGetOpenFilename()

    Do

        fName = Application.GetOpenFilename

    Loop Until fName <> False

    MsgBox "Opening " & fName

    Set myBook = Workbooks.Open(Filename:=fName)

End Sub

 

Creating and Saving Workbooks

You create a new workbook by applying the Add method to the Workbooks collection. Remember to set the return value of the Add method to an object variable so that you can refer to the new workbook in your code. 

When you save a new workbook for the first time, use the SaveAs method. For subsequent saves, use the Save method. The GetSaveAsFilename method is very similar to the GetOpenFilename method, which is described in the preceding section. The following example shows how to create a new workbook and then save it using the GetSaveAsFilename method. 

Sub CreateAndSave()

    Set newBook = Workbooks.Add

    Do

        fName = Application.GetSaveAsFilename

    Loop Until fName <> False

    newBook.SaveAs Filename:=fName

End Sub

 

Closing Workbooks

To close a workbook, use the Close method of the Workbook object. You can close a workbook without saving changes, as shown in the following example. 

Sub OpenChangeClose()

    Do

        fName = Application.GetOpenFilename

    Loop Until fName <> False

    Set myBook = Workbooks.Open(Filename:=fName)

    ' Make some changes to myBook

    myBook.Close savechanges:=False

End Sub

 

This code uses the GetOpenFilename method to select the workbook to open, makes some changes to the workbook (indicated by the comments), and then closes the workbook without saving the changes.

Working with the Range Object

The Range object can represent a single cell, a range of cells, an entire row or column, a selection containing multiple areas, or a 3D range. The Range object is somewhat unusual in that it can represent both a single cell and multiple cells. There's no separate collection object for the Range object; you can think of it as being either a single object or a collection, depending on the situation. There are many different properties and methods that return a Range object, as shown in the following list. 

ActiveCell 

BottomRightCell 

Cells 

ChangingCells 

CircularReference 

Columns 

CurrentArray 

CurrentRegion 

Dependents 

DirectDependents 

DirectPrecedents 

EntireColumn 

EntireRow 

Next 

Offset 

PageRange 

Precedents 

Range 

RowFields 

RowRange 

Rows 

Selection 

TableRange1 

TableRange2 

TopLeftCell 

UsedRange 

VisibleRange 

For more information about these properties and methods, see the individual property and method topics in Help. 

Using an A1Style String Reference or Range Name

One of the most common ways to return a Range object is to use an A1style reference or the name of a range, as shown in the following table. 

To do this

Use the following code 

Set the value of cell A1 on Sheet1 

Worksheets("Sheet1").Range("A1").Value = 3 

Set the formula for cell B1 on the active sheet 

Range("B1").Formula = "=5-10*RAND()" 

Set the value of each cell in the range C1:E3 on the active sheet 

Range("C1:E3").Value = 6 

Clear the contents of the range A1:E3 on the active sheet 

Range("A1", "E3").ClearContents 

Set the font style for the range named "myRange" (a workbook-level name) to bold 

Range("myRange").Font.Bold = True 

Set the value of each cell in the range named "yourRange" (a sheet-level name) 

Range("Sheet1!yourRange").Value = 3 

Set an object variable to refer to a range 

Set objRange = Range("myRange") 

Remember that expressions such as Range("C1:E3").Value = 6 assume that the Range property operates on the active sheet. If you try to run this code with a chart sheet active, a runtime error occurs (error 1004, "Range method of Application class failed"). 

Another cause of errors is the use of the Range property in an argument to another method, without fully qualifying the Worksheet object to which the Range property applies. The following example, which is supposed to sort a range of cells on Sheet1, also causes runtime error 1004. 

Sub SortRange()

Worksheets("Sheet1").Range("A1:B10").Sort _

    key1:=Range("A1"), order1:=xlDescending

End Sub



This error is more difficult to find, because the line that contains the Sort method is correct. The error is caused by the second line, which contains the Key1 argument. This code will run correctly if Sheet1 is the active sheet, but it will fail when it's run from another worksheet or from a module. To avoid the error, use the Worksheets property in the argument. 

Sub SortRange()

    Worksheets("Sheet1").Range("A1:B10").Sort _

        key1:=Worksheets("Sheet1").Range("A1"), order1:=xlDescending

End Sub

 

Using Numeric Row and Column Indexes

You can also return a specific cell by specifying its row and column numbers, or indexes. You specify the row index first, followed by the column index, as shown in the following table.

To do this

Use the following code 

Set the value of cell A1 on Sheet1 

Worksheets("Sheet1").Cells(1, 1).Value = 3 

Set the formula for cell B1 on the active sheet 

Cells(1, 2).Formula = "=5-10*RAND()" 

Set an object variable 

Set objRange = Worksheets("Sheet1").Cells(1, 1) 

Numeric row and column indexes are useful when you want to refer to cells by using loop counters. For example, the following code loops through cells A1:D10 on Sheet1. If any of the cells has a value less than 0.01, the example replaces the value with 0 (zero). 

Sub RoundToZero()

    For rwIndex = 1 to 4

        For colIndex = 1 to 10

            If Worksheets("Sheet1").Cells(rwIndex, colIndex) < .01 Then

                Worksheets("Sheet1").Cells(rwIndex, colIndex).Value = 0

            End If

        Next colIndex

    Next rwIndex

End Sub



The following example shows a quick and easy way to display items in a multiplecolumn list. The code creates a new worksheet and sets the object variable newSheet to refer to the worksheet. The code then creates a list of all the names in the active workbook and displays their formulas in A1style notation. 

Sub ListNames()

    Set newSheet = Worksheets.Add

    i = 1

    For Each nm In ActiveWorkbook.Names

        newSheet.Cells(i, 1).Value = nm.Name

        newSheet.Cells(i, 2).Value = "'" & nm.RefersTo

        i = i + 1

    Next nm

    newSheet.Columns("A:B").AutoFit

End Sub

 

Using the Offset Property

You often need to return a range of cells that's a certain number of rows or columns away from another range of cells. The Offset property applies to a Range object, takes a RowOffset argument and a ColumnOffset argument, and returns a new range. The following example determines the type of data in each cell in the range A1:A10. The code writes the data types in the column to the right of the input cells. 

Sub ScanColumn()

    For Each c In Worksheets("Sheet1").Range("A1:A10").Cells

        If Application.IsText(c.Value) Then

            c.Offset(0, 1).Formula = "Text"

        ElseIf Application.IsNumber(c.Value) Then

            c.Offset(0, 1).Formula = "Number"

        ElseIf Application.IsLogical(c.Value) Then

            c.Offset(0, 1).Formula = "Boolean"

        ElseIf Application.IsError(c.Value) Then

            c.Offset(0, 1).Formula = "Error"

        ElseIf c.Value = "" Then

            c.Offset(0, 1).Formula = "(blank cell)"

        End If

    Next c

End Sub

 

Using the CurrentRegion and UsedRange Properties

These two properties are very useful when your code operates on ranges whose size you have no control over. The current region is a range of cells bounded by empty rows and empty columns, or by a combination of empty rows, empty columns, and the edges of the worksheet.

The CurrentRegion property applies to a Range object. There can be many different current regions on a worksheet, depending on the Range object to which you apply the CurrentRegion property. 

Suppose that Sheet1 contains a list to which you want to apply a number format. The only thing you know about the list is that it begins at cell A1; you don't know how many rows or columns it contains. The following example shows how to format the list by using the CurrentRegion property. 

Sub FormatRange()

    Set myRange = Worksheets("Sheet1").Range("A1").CurrentRegion

    myRange.NumberFormat = "0.0"

End Sub

 

The used range is bounded by the farthest upperleft and farthest lowerright nonempty cells on a worksheet. It's a range that contains every nonempty cell on the worksheet, as well as all the empty cells that are interspersed among them. There can be only one used range on a worksheet; the UsedRange property applies to a Worksheet object, not to a Range object. 

Suppose that the active worksheet contains data from a timed experiment. The used range contains the dates in the first column, the times in the second column, and the measurements in the third and fourth columns. You want to write code that combines each separate date and time into a single value, converts that value from Greenwich Mean Time (GMT) to Pacific Standard Time (PST), and then applies a date format to the value. The data table can contain empty rows and columns. You can use the UsedRange property to return the entire used range, including any embedded blank rows. The following example shows one way to convert and format the dates and times. 

Sub ConvertDates()

    Set myRange = ActiveSheet.UsedRange

    myRange.Columns("C").Insert

    Set dateCol = myRange.Columns("C")

    For Each c In dateCol.Cells

        If c.Offset(0, -1).Value <> "" Then

            c.FormulaR1C1 = "=RC[-2]+RC[-1]-(8/24)"

        End If

    Next c

    dateCol.NumberFormat = "mmm-dd-yyyy hh:mm"

    dateCol.Copy

    dateCol.PasteSpecial Paste:=xlValues

    myRange.Columns("A:B").Delete

    dateCol.AutoFit

End Sub



Notice that the code uses the expression ActiveSheet.UsedRange.Columns("C") to return the third column from the used range (although this is the third column in the used range, it can appear in any column on the worksheet — that is, the used range can be preceded by empty columns). You can use other Range object properties and methods in a similar way to build complex expressions that return subranges or superranges of a Range object. Some properties and methods commonly used in this way are Areas, Cells, Columns, EntireColumn, EntireRow, Range, and Rows

Looping on a Range of Cells

There are several different ways to loop on the cells in a range. The examples in this section show the For Each...Next statement and the Do...Loop statement applied to looping on a range of cells. 

Using For Each...Next

The recommended way to loop on the cells in a range is to use the For Each...Next loop, which is also the recommended way to loop on the elements in a collection. 

The following example shows how to loop through the range A1:D10 on Sheet1, setting any number whose absolute value is less than 0.01 to 0 (zero). 

Sub RoundToZero()

    For Each r In Worksheets("Sheet1").Range("A1:D10").Cells

        If Abs(r.Value) < 0.01 Then

            r.Value = 0

        End If

    Next r

End Sub

 

Suppose that you want to modify this code to loop over a range of cells that a user selects. One way of doing this is to use the InputBox method to prompt the user to select a range of cells. The InputBox method returns a Range object that represents the selection. By using the Type argument and error handling, you can ensure that the user selects a valid range of cells before the input box is dismissed. 

Sub RoundToZero()

    Worksheets("Sheet1").Activate

    On Error GoTo PressedCancel

    Set r = Application.InputBox( _

            prompt:="Select a range of cells", _

            Type:=8)

    On Error GoTo 0

    For Each c In r.Cells

        If Abs(c.Value) < 0.01 Then

            c.Value = 0

        End If

    Next c

    Exit Sub

 

PressedCancel:

    Resume

End Sub

 

If you don't want the user to select the range, you may be able to use the CurrentRegion property or the UsedRange property to return a Range object. For example, if you know that the data on Sheet1 begins at cell A1 and includes no empty rows or columns, you can use the CurrentRegion property to return the entire range automatically. 

Sub RoundToZero()

    Set r = Worksheets("Sheet1").Range("A1").CurrentRegion

    For Each c In r.Cells

        If Abs(c.Value) < 0.01 Then

            c.Value = 0

        End If

    Next c

End Sub

 

The following two examples show two different ways to hide every other column in the used range on Sheet1. The first example shows a For Each...Next loop in which the Column property of the object variable is tested. 

Sub HideColumns()

    Set r = Worksheets("Sheet1").UsedRange

    For Each col In r.Columns

        If col.Column Mod 2 = 0 Then

            col.Hidden = True

        End If

    Next col

End Sub

 

The second example shows a For...Next loop that tests the loop counter. 

Sub HideColumns()

    Set r = Worksheets("Sheet1").UsedRange

    For i = 1 To r.Columns.Count

        If i Mod 2 = 0 Then

            r.Columns(i).Hidden = True

        End If

    Next i

End Sub

 

Using Do...Loop

Occasionally, the For Each...Next loop isn't the best way to loop on a range. Suppose that you have a column of data and you want to write a macro that sorts the data and then deletes rows that contain duplicate data. You could try to use a For Each...Next loop, as shown in the following example. 

Sub BuggyRemoveDuplicates()    ' DON'T USE THIS CODE!

    Worksheets("Sheet1").Range("A1").Sort _

        key1:=Worksheets("Sheet1").Range("A1")

    Set r = Worksheets("Sheet1").Range("A1").CurrentRegion.Columns("A")

    For Each c In r.Cells

        If c.Offset(1, 0).Value = c.Value Then

            c.Offset(1, 0).EntireRow.Delete

        End If

    Next c

End Sub

 

Unfortunately, this code doesn't work correctly because the Delete method is modifying the range on which For Each...Next is looping. This causes duplicates not to be deleted in some cases. 

A better solution is to use a Do...Loop structure, as shown in the following example. 

Sub GoodRemoveDuplicates()

    Worksheets("Sheet1").Range("A1").Sort _

            key1:=Worksheets("Sheet1").Range("A1")

    Set currentCell = Worksheets("Sheet1").Range("A1")

    Do While Not IsEmpty(currentCell)

        Set nextCell = currentCell.Offset(1, 0)

        If nextCell.Value = currentCell.Value Then

            currentCell.EntireRow.Delete

        End If

        Set currentCell = nextCell

    Loop

End Sub

 

The loop tests the object variable currentCell, exiting when it encounters an empty cell at the bottom of the column of data. You could build an equivalent loop by testing the value in currentCell against an empty string, as shown in the following example. 

    Do While currentCell.Value <> ""

        ' Code to run on cells with values

    Loop

 

In either case, don't forget to increment the cell at the bottom of the Do...Loop structure (Set currentCell = nextCell, for example). 

Using the Address Property to Debug Range Object Code

You can apply the Address property to any Range object. The Address property returns the cell address of a range, as a string. The following example shows how to use the Address property to debug the HideColumns procedure. 

Sub HideColumns()

    Set r = Worksheets("Sheet1").UsedRange

    MsgBox r.Address  ' debugging only!

    For i = 1 To r.Columns.Count

        If i Mod 2 = 0 Then

            r.Columns(i).Hidden = True

            MsgBox r.Columns(i).Address  ' debugging only!

        End If

    Next i

End Sub

 

You can also set watch expressions instead of using message boxes. For the preceding example, you could set two watch expressions — r.Address and r.Columns(i).Address — and then examine the values of the watch expressions in the Immediate window. For more information about debugging, see Chapter 14, "Debugging and Error Handling."

Working with Events

If you've used Visual Basic (Standard, Professional, or Enterprise Edition), you're familiar with eventdriven programming; most of your Visual Basic code was probably written to respond to events, such as when the user clicks a button or when a form is loaded. In Microsoft Excel, you may have used properties such as OnSheetActivate or OnEntry to cause a macro to run when a sheet is activated or changed. This is also eventdriven programming. Microsoft Excel 97 expands the available list of events and adds event procedures that receive arguments. 

With Microsoft Excel 97, you can write event procedures at the worksheet, chart, workbook, or application level. For example, the Activate event occurs at the sheet level, and the SheetActivate event is available at both the workbook and application levels. The SheetActivate event for a workbook occurs when any sheet in that workbook is activated. At the application level, the SheetActivate event occurs when any sheet in any open workbook is activated.

Worksheet and workbook event procedures are created by default for any open worksheet, chart sheet, or workbook. To write event procedures for an embedded chart or for the application, you must create a new object using the WithEvents keyword in a class module. You can also use a class module to create event procedures that can be used for more than one worksheet or workbook. For more information , see "Using Class Modules with Events" later in this chapter. 

Enabling or Disabling Events

Use the EnableEvents property to enable or disable events. For example, using the Save method to save a workbook causes the BeforeSave event to occur. You can prevent this by setting the EnableEvents property to False before you call the Save method, as in the following example. 

Application. = False

ActiveWorkbook.Save

Application. = True

 

Using Events on Sheets

Events on sheets are enabled by default. To view the event procedures for a particular sheet, use either of the following techniques:

  • Rightclick the sheet tab, and then click View Code on the shortcut menu. In the Procedure box, click the event name. 

  • On the Tools menu, point to Macro and then click Visual Basic Editor. Select the sheet in the Project Explorer, and then either click the View Code button or click Code on the View menu. In the Object box, click either Worksheet or Chart, and then click the event name in the Procedure box.

Worksheet Events

Worksheetlevel events occur when the user activates a worksheet or changes a worksheet cell, as shown in the following table.

Event

Description 

Activate

Occurs when the user activates the sheet. Use this event instead of the OnSheetActivate property. 

BeforeDoubleClick

Occurs when the user double-clicks a worksheet cell. Use this event instead of the OnDoubleClick property. 

BeforeRightClick

Occurs when the user right-clicks a worksheet cell. 

Calculate

Occurs when the user recalculates the worksheet. Use this event instead of the OnCalculate property. 

Change

Occurs when the user changes a cell formula. Use this event instead of the OnEntry property. 

Deactivate

Occurs when the sheet is active and the user activates a different sheet. Doesn't occur when the user shifts the focus from one window to another window showing the same sheet. Use this event instead of the OnSheetDeactivate property. 

SelectionChange

Occurs when the user selects a worksheet cell. 

For more information about an event, see the corresponding Help topic. 

Examples 

The following example adjusts the size of columns A through F whenever the worksheet is recalculated. 

Private Sub Worksheet_Calculate()

    Columns("A:F").AutoFit

End Sub

 

Some events can be used to substitute an action for the default application behavior, or to make a small change to the default behavior. The following example traps the rightclick event and adds a new menu item to the shortcut menu for cells B1:B10. 

Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, _

        Cancel As Boolean)

    For Each icbc In Application.CommandBars("cell").Controls

        If icbc.Tag = "brccm" Then icbc.Delete

    Next icbc

    If Not Application.Intersect(Target, Range("b1:b10")) Is Nothing Then

            With Application.CommandBars("cell").Controls _

                .Add(Type:=msoControlButton, before:=6, _

                temporary:=True)

           .Caption = "New Context Menu Item"

           .OnAction = "MyMacro"

           .Tag = "brccm"

        End With

    End If

End Sub

 

Chart Events

Like worksheetlevel events, chartlevel events occur when the user activates or changes a chart, as shown in the following table.

Event

Description 

Activate

Occurs when the user activates the chart sheet (doesn't work with embedded charts). Use this event instead of the OnSheetActivate property. 

BeforeDoubleClick

Occurs when the user double-clicks the chart. Use this event instead of the OnDoubleClick property. 

BeforeRightClick

Occurs when the user right-clicks the chart. 

Calculate

Occurs when the user plots new or changed data on the chart. 

Deactivate

Occurs when the sheet is active and the user activates a different sheet. Doesn't occur when the user shifts the focus from one window to another window showing the same sheet. Use this event instead of the OnSheetDeactivate property. 

DragOver

Occurs when the user drags data over the chart. 

DragPlot

Occurs when the user drags a range of cells over the chart. 

MouseDown

Occurs when the user clicks a mouse button while the pointer is positioned over the chart. 

MouseMove

Occurs when the user moves the pointer over the chart. 

MouseUp

Occurs when the user releases a mouse button while the pointer is positioned over the chart. 

Resize

Occurs when the user changes the size of the chart. 

Select

Occurs when the user selects a chart element. 

SeriesChange

Occurs when the user changes the value of a chart data point. 

For more information about an event, see the corresponding Help topic.

Events for chart sheets are available by default in the Visual Basic Editor. To write event procedures for an embedded chart, you must create a new object using the WithEvents keyword in a class module. For more information, see "Using Class Modules with Events" later in this chapter. 

Example 

The following example changes a point's border color when the user changes the point's value. 

Private Sub Chart_SeriesChange(ByVal SeriesIndex As Long, _

        ByVal PointIndex As Long)

    Set p = ActiveChart.SeriesCollection(SeriesIndex).Points(PointIndex)

    p.Border.ColorIndex = 3

End Sub

 

Workbook Events

Workbook events occur when the user changes a workbook or any sheet in the workbook.

Event

Description 

Activate

Occurs when the user activates the workbook. 

AddInInstall

Occurs when the user installs the workbook as an addin. Use this event instead of the Auto_Add macro. 

AddInUninstall

Occurs when the user uninstalls the workbook as an add-in. Use this event instead of the Auto_Remove macro. 

BeforeClose

Occurs before the workbook closes. Use this event instead of the Auto_Close macro. 

BeforePrint

Occurs before the workbook is printed. 

BeforeSave

Occurs before the workbook is saved. Use this event instead of the OnSave property. 

Deactivate

Occurs when the workbook is active and the user activates a different workbook. 

NewSheet

Occurs after the user creates a new sheet. 

Open

Occurs when the user opens the workbook. Use this event instead of the Auto_Open macro. 

SheetActivate

Occurs when the user activates a sheet in the workbook. Use this event instead of the OnSheetActivate property. 

SheetBeforeDoubleClick

Occurs when the user double-clicks a worksheet cell (not used with chart sheets). Use this event instead of the OnDoubleClick property. 

SheetBeforeRightClick

Occurs when the user right-clicks a cell on a worksheet (not used with chart sheets). 

SheetCalculate

Occurs after the user recalculates a worksheet (not used with chart sheets). Use this event instead of the OnCalculate property. 

SheetChange

Occurs when the user changes a cell formula (not used with chart sheets). Use this event instead of the OnEntry property. 

SheetDeactivate

Occurs when the user activates a different sheet in the workbook. Use this event instead of the OnSheetDeactivate property. 

SheetSelectionChange

Occurs when the user changes the selection on a worksheet (not used with chart sheets). 

WindowActivate

Occurs when the user shifts the focus to any window showing the workbook. Use this event instead of the OnWindow property. 

WindowDeactivate

Occurs when the user shifts the focus away from any window showing the workbook. Use this event instead of the OnWindow property. 

WindowResize

Occurs when the user opens, resizes, maximizes, or minimizes any window showing the workbook. 

For more information about an event, see the corresponding Help topic. 

Example 

The following example maximizes the Microsoft Excel application window when the workbook is opened. 

Sub Workbook_Open()

    Application.WindowState = xlMaximized

End Sub

 

Application Events

Application events occur when the user creates or opens a workbook or when the user changes any sheet in any open workbook.

Event

Description 

NewWorkbook

Occurs when the user creates a new workbook. 

SheetActivate

Occurs when the user activates a sheet in an open workbook. Use this event instead of the OnSheetActivate property. 

SheetBeforeDoubleClick

Occurs when the user double-clicks a worksheet cell in an open workbook (not used with chart sheets). Use this event instead of the OnDoubleClick property. 

SheetBeforeRightClick

Occurs when the user right-clicks a worksheet cell in an open workbook (not used with chart sheets). 

SheetCalculate

Occurs after the user recalculates a worksheet in an open workbook (not used with chart sheets). Use this event instead of the OnCalculate property. 

SheetChange

Occurs when the user changes a cell formula in an open workbook (not used with chart sheets). Use this event instead of the OnEntry property. 

SheetDeactivate

Occurs when the user deactivates a sheet in an open workbook. Use this event instead of the OnSheetDeactivate property. 

SheetSelectionChange

Occurs when the user changes the selection on a sheet in an open workbook. 

WindowActivate

Occurs when the user shifts the focus to an open window. Use this event instead of the OnWindow property. 

WindowDeactivate

Occurs when the user shifts the focus away from an open window. Use this event instead of the OnWindow property. 

WindowResize

Occurs when the user resizes an open window. 

WorkbookActivate

Occurs when the user shifts the focus to an open workbook. 

WorkbookAddInInstall

Occurs when the user installs a workbook as an add-in. 

WorkbookAddInUninstall

Occurs when the user uninstalls a workbook as an add-in. 

WorkbookBeforeClose

Occurs before an open workbook is closed. 

WorkbookBeforePrint

Occurs before an open workbook is printed. 

WorkbookBeforeSave

Occurs before an open workbook is saved. 

WorkbookDeactivate

Occurs when the user shifts the focus away from an open workbook. 

WorkbookNewSheet

Occurs when the user adds a new sheet to an open workbook. 

WorkbookOpen

Occurs when the user opens a workbook. 

For more information about an event, see the corresponding Help topic. 

Using Class Modules with Events

Unlike sheet events, embedded charts and the Application object don't have events enabled by default. Before you can use events with an embedded chart or with the Application object, you must create a new class module and declare an object of type Chart or Application with events. You use the Class Module command (Insert menu) in the Visual Basic Editor to create a new class module. 

To enable the events of the Application object, you'd add the following declaration to the class module. 

Public WithEvents App As Application

 

After the new object has been declared with events, it appears in the Object box in the class module, and you can write event procedures for the new object. (When you select the new object in the Object box, the valid events for that object are listed in the Procedure box.) 

Before the procedures will run, however, you must connect the declared object in the class module to the Application object. You can do this from any module by using the following declaration (where "EventClass" is the name of the class module you created to enable events). 

Public X As New EventClass

 

After you've created the X object variable (an instance of the EventClass class), you can set the App object of the EventClass class equal to the Microsoft Excel Application object. 

Sub InitializeApp()

    Set X.App = Application

End Sub

 

After you run the InitializeApp procedure, the App object in the EventClass class module points to the Microsoft Excel Application object, and the event procedures in the class module will run whenever the events occur. 

Although this may seem like a lot of work, one advantage is that you can use the same event procedure for many objects. For example, suppose that you declare an object of type Chart with events in a class module, as follows. 

Public WithEvents cht As Chart

 

You can then use the following code to cause the event procedures to run whenever an event occurs for either chart one or chart two. 

Dim C1 As New EventClass

Dim C2 As New EventClass

 

Sub InitializeCharts

    Set C1.cht = Worksheets(1).ChartObjects(1).Chart

    Set C2.cht = Worksheets(1).ChartObjects(2).Chart

End Sub

 

You can declare Worksheet or Workbook objects with events in a class module and use the events in the new class with several sheets, in addition to the default event procedures. You might use this technique to write an Activate event handler that runs only when either sheet one or sheet five is activated. Or you can use a Chart object declared in a class module to write an event handler for both embedded charts and chart sheets. 





This page lists some suggestions and strategies for optimizing your Visual Basic For Applications (VBA) code, so that it will run faster.   There are few absolute rules for optimizing VBA;  you'll see the best increases in performance by streamlining the basic logic. 


Accessing Cells In A Range


You do not need to use the .Cells method to access specific cells in a range.  For example, you can use

Range("MyRange")(1,2)   rather than
Range("MyRange").Cells(1,2)

See Alan Beban's explanation of this method for more details.

Related to this is the shortcut method of refering to cells.  VBA will allow you reference cells with [A1] rather than Range("A1"). While the [A1] syntax is easier to type, it is slower to execute than the Range("A1") syntax.

 

Calculation Mode


Normally, Excel will recalculate a cell or a range of cells when that cell's or range's precedents have changed.  This may  cause your workbook to recalculate too often, which will slow down performance.  You can prevent Excel from recalculating the workbook by using the statement:

Application.Calculation = xlCalculationManual

At the end of your code, you can set the calculation mode back to automatic with the statement:

Application.Calculation = xlCalculationAutomatic

Remember, though, that when the calculation mode is xlCalculationManual, Excel doesn't update values in cells.  If your macro relies on an updated cell value, you must force a Calculate event, with the .Calculate method, which may be applied to either a specific range (Range("MyRange").Calculate) or to the entire workbook (Calculate).

 

Collection Indexes


An individual item of a collection object may be accessed by either its name or by its index into the collection.  For example, if you have three worksheets ("Sheet1", "Sheet2", and "Sheet3")  in a workbook ("MyWorkbook"), you can reference "Sheet2" with either

Worksheets("Sheet2")  or
Worksheets(2)

In general, the index number method (Worksheets(2)) is considerably faster than the index name method (Worksheets("Sheet2")). 

However, the number and order of items in a collection may change, so it is usually safer and easier to refer to items in a collection by their name, rather than their index number.

Constants


Whenever you can, declare values as constants, rather than variables.   Since their values never change, they are evaluated only once when your code is compiled, rather than each time they are used at run time.

 

Early Binding


This is closely tied with Specific Object Type Declaration.  If you're going to work with another application, such as Word, declare your OLE object directly, rather than as an Object type variable.  By doing so, a great deal of overhead is done at compile time ("Early Binding") rather than at run time ("Late Binding").   For example, use

Dim WordObj As Word.Application        rather than
Dim WordObj As Object

Also see Using Variables (Properly) In VBA.

FOR EACH Loops 


When looping through a collection it is usually faster than the FOR EACH statement rather than using the index.  For example, the first code loop is faster than the second: 

Dim WS as Worksheet
For Each WS In Worksheets
    MsgBox WS.Name
Next WS

Dim i as Integer
For i = 1 To Worksheets.Count
    MsgBox Worksheets(i).Name
Next i

 

Range Objects Not Selection Object


Generally, it is not necessary to select a range before working with it.  For example, it is more efficient to use

Range("A1").Font.Bold = True

Rather than

Range("A1").Select
Selection.Font.Bold = True

 

Screen Updating


You can turn off screen updating so that Excel does not update the screen image as your code executes.  This can greatly speed up your code.

Application.ScreenUpdating = FALSE

Be sure to restore the setting to True at the end of your macro.  Older version of Excel would automatically restore the setting; Excel97 does not.

 

Simple Objects Rather Than Compound Objects


If you've got to make repeated references to an object, such a range, declare an object of that type, set it to the target object, and then use your object to refer to the target.   For example,

Dim MyCell As Range
Set MyCell = Workbooks("Book2").Worksheets("Sheet3").Range("C3")
'....
MyCell.Value = 123

By referring directly to MyCell , VBA can access the object directly, rather than resolving the complete path to the object each time.  This method is useful only when you are accessing an object several times during code execution.

 

Specific Object Type Declaration


If possible avoid using the Object or Variant data types.  These data types require considerable overhead to determine their types.  Instead, use explicit data types, such as

Dim MySheet As Worksheet  rather than
Dim MySheet As Object 
Or
Dim NumRows As Long rather than
Dim NumRows As Variant

This is especially true with index variables in For Next loops, since a Variant type has to be re-evaluated each iteration of the loop.

Also see Using Variables (Properly) In VBA.

 

WITH Statements


If you are using several statement in a row that apply to the same object, use a WITH statement, rather than fully qualifying the object each time.  For example,

With Worksheets("Sheet1").Range("A1")
    .Font.Bold = True
    .Value = 123
End With



Worksheet Functions


You can use Excel's standard worksheet functions in your VBA code, rather than writing the functions in VBA.  Since these are fully executable instructions in native code, rather than interpreted VBA code, they run much faster.  For example, use

MySum = Application.WorksheetFunction.Sum(Range("A1:A100"))   

rather than

For Each C In Range("A1:A100")
    MySum = MySum + C.Value
Next C