C H A P T E R 13 | Microsoft Office 97/Visual Basic Programmer's Guide |
Optimizing for Size and Speed |
Use the following techniques for optimizing your Microsoft Excel, Word, and PowerPoint code.
Note Most of the example code in this section was written in Microsoft Excel, but the principles also apply to Word and PowerPoint. For information about optimizing Visual Basic in Microsoft Access, see Building Applications with Microsoft Access 97.
Minimizing OLE References
Every Visual Basic method or property call requires one or more
calls through the OLE IDispatch interface. These OLE calls take
time. Minimizing the number of method or property calls is one
of the best ways to make your macro run faster.
Because you use a period (a "dot") to separate the parts
of a Visual Basic statement, an easy way to keep track of the
number of method and property calls is to "count the dots."
For example, the following statement contains three dots.
The following statement contains only one dot.
Using Object Variables
If you find that you're using the same object reference over and
over, you can set a variable for the object and subsequently use
the variable in place of the object reference. This way, you'll
only need to call the object accessor once, when you set the variable,
instead of calling it each time you want to refer to the object.
The following example calls the Workbooks method and the
Sheets method twice each.
You can optimize the preceding example by setting an object variable.
The following example calls the Workbooks method and the
Sheets method only once each.
Using the With Statement
You can use the With statement to eliminate the need for
repetitive object references, without setting an explicit object
variable. The example in the preceding section could be rewritten
as follows, using the With statement. The following example
calls the Workbooks method and the Sheets method
only once each.
Using the With statement eliminates the need for the intermediate
variable used in the example in the preceding section; otherwise,
this code is the same as in that example.
Using a For Each...Next Loop
Using a For Each...Next loop to iterate through a collection
or array is faster than using an indexed loop. In most cases,
using a For Each...Next loop is also more convenient and
makes your macro smaller and easier to read and debug.
The following example is slow because it sets the row variable
thisRow by
calling r.Rows(i)
each time through the loop.
Keeping Properties and Methods Outside Loops
Your code can get variable values faster than it can get property
values. Therefore, if your code gets the value of a property within
a loop, it will run faster if you assign the property to a variable
outside the loop and use the variable instead of the property
inside the loop. The following example is slow because it gets
the Value property each time through the loop.
The following example is faster than the preceding one because
the value of one property has been assigned to the variable cv
before the loop begins. Visual Basic must therefore access only
one property value (instead of two) each time through the loop.
Rewriting this example by using the With statement moves
the ActiveWorkbook property and Sheets property
calls outside the loop. You could also move these calls outside
the loop by using an object variable.
Using Collection Index Numbers
With most object accessor methods and properties, you can specify
an individual object in a collection either by name or by number.
Using the object's index number is usually faster. If you use
the object's name, Visual Basic must resolve the name to the index
value; if you use the index value, you avoid this extra step.
There are, however, some significant advantages to specifying
an object in a collection by name. One advantage is that using
an object's name makes your code easier to read and debug. In
addition, specifying an object by name is safer than specifying
it by index number, because the index value for an object can
change while your code is running. For example, a menu's index
number represents the menu's position on the menu bar; therefore,
the index number can change if menus are added to or deleted from
the menu bar. This is one instance where faster isn't necessarily
better. You should use this technique only when you're sure that
the index value cannot change.
Minimizing Object Activation and Selection
Most of the time, your code can operate on objects without activating
them. If you learned Visual Basic programming by using the macro
recorder, you're probably accustomed to activating or selecting
an object before you do anything to that object. The macro recorder
does this because it must follow your keystrokes as you activate
windows and select their contents. However, you can usually write
much simpler and faster Visual Basic code that produces the same
results without activating or selecting each object before working
with it. For example, filling cells C1:C20 on Sheet5 with random
numbers (using the AutoFill method) produces the macro
recorder output shown in the following example.
Keep in mind that the macro recorder records exactly what you
do it cannot optimize anything on its own. The
recorded macro uses the AutoFill method because that's
how the user entered the random numbers. This isn't the most efficient
way to fill a range with random numbers. You can do the same thing
with a single line, as shown in the following example.
When you optimize recorded code, think about what you're trying
to do with the macro. Some of the operations you can perform in
the user interface (such as dragging a formula from a single cell
into a range) are recorded as methods (such as AutoFill)
that can be eliminated in the optimized code because there's a
faster way to perform the same operation in Visual Basic.
Removing Unnecessary Recorded Expressions
Another reason why the macro recorder produces inefficient code
is that it cannot tell which options you've changed in a dialog
box. The recorder therefore explicitly sets all available options
when you close the dialog box. For example, selecting cells B2:B14
and then changing the font style to bold in the Format Cells
dialog box produces the recorded macro shown in the following
example.
You can set the font style for the specified cell to bold with
a single line of code and without selecting the range, as shown
in the following example.
Again, if you think about what you're trying to do with the macro
and you look through the lists of properties and methods that
apply to the Font object, you'll see that you could also
write this macro using the Bold property, as shown in the
following example.
Minimizing the Use of Variant Variables
Although you may find it convenient to use Variant variables
in your code, Visual Basic requires more time to process a value
stored in a Variant variable than it needs to process a
value stored in a variable declared with an explicit data type.
Your code can perform mathematical computations that don't involve
fractional values faster if you use Integer or Long
variables rather than Variant variables. Integer
or Long variables are also the best choice for the index
variable in For...Next loops. The speed you gain using
explicit variable types can come at the expense of flexibility.
For example, when using explicit data types, you may encounter
cases of overflow that Variant variables handle automatically.
Using Specific Object Types
References to objects and their methods and properties are resolved
either when your macro is compiled or when it runs. References
that are resolved when the macro is compiled are faster than references
that must be resolved while the macro is running.
If you declare variables and arguments as specific object types
(such as Range or Worksheet), Visual Basic can resolve
references to the properties and methods of those objects when
your macro is compiled. For a list of specific object types, see
the Object Browser.
In addition, you should use fully qualified object references.
This eliminates ambiguity and ensures that the variable has the
intended type. A fully qualified object reference includes the
library name, as shown in the following examples.
If you declare variables and arguments with the generic Object
data type, Visual Basic may have to resolve references to their
properties and methods when it encounters them at run time, resulting
in a significantly slower process.
Using Constants
Using constants in an application makes the application run faster.
Constants are evaluated once and are stored when your code is
compiled. Variables can change, though, so Visual Basic must get
the current variable value each time the macro runs. Constants
also make your macros more readable and easier to maintain. If
there are strings or numbers in a macro that don't change, declare
them as constants.
Turning Off Screen Updating
A macro that makes changes to the appearance of a document
such as a macro that changes the color of every other cell in
a large range or that creates a large number of graphic objects
will run faster when screen updating is turned off. You won't
be able to watch the macro run (the changes will appear all at
once when you turn screen updating back on), but it will run much
faster. You may want to leave screen updating turned on while
you write and debug the macro, and then turn it off before you
run the macro.
To turn off screen updating, set the ScreenUpdating property
to False, as shown in the following example.
Tip You
can sometimes achieve the same effect by not activating the object
you're changing. For example, if you create graphic objects on
a sheet without first activating the document, you don't need
to turn screen updating off because the changes won't be visible
anyway.
Workbooks(1).Sheets(1).Range("c5").Value = 10
The examples in the following sections demonstrate how reducing
the number of dots creates fasterrunning code.
ActiveWindow.Left = 200
Workbooks(1).Sheets(1).Range("c5").Value = 10
Workbooks(1).Sheets(1).Range("d10").Value = 12
Set sheet = Workbooks(1).Sheets(1)
sheet.Range("c5").Value = 10
sheet.Range("d10").Value = 12
With Workbooks(1).Sheets(1)
.Range("c5").Value = 10
.Range("d10").Value = 12
End With
The following example is faster and smaller than the preceding
one because the For Each...Next loop keeps track of the
row count and position.
Set r = Worksheets(1).Range("a1:a200")
For i = 1 To r.Rows.Count
Set thisRow = r.Rows(i)
If thisRow.Cells(1, 1).Value < 0 Then
thisRow.Font.Color = RGB(255, 0, 0)
End If
Next
For Each thisRow In Worksheets(1).Range("a1:a200").Rows
If thisRow.Cells(1, 1).Value < 0 Then
thisRow.Font.Color = RGB(255, 0, 0)
End If
Next
For iLoop = 2 To 200
Cells(iLoop, 1).Value = Cells(1, 1).Value
Next
If you're using an object accessor inside a loop, try to move
it outside the loop. The following example calls the ActiveWorkbook
property, the Sheets property, and the Cells property
each time through the loop.
cv = Cells(1, 1).Value
For i Loop = 2 To 200
Cells(iLoop, 1).Value = cv
Next
For c = 1 To 1000
ActiveWorkbook.Sheets(1).Cells(c, 1) = c
Next
With ActiveWorkbook.Sheets(1)
For c = 1 To 1000
.Cells(c, 1) = c
Next
End With
All of the Select method calls are unnecessary. You can
use the With statement to write code that operates directly
on the worksheet and the range, as shown in the following example.
Sheets("Sheet5").Select
Range("C1").Select
ActiveCell.FormulaR1C1 = "=RAND()"
Selection.AutoFill Destination:=Range("C1:C20"), Type:=xlFillDefault
Range("C1:C20").Select
With Sheets("Sheet5")
.Range("C1").FormulaR1C1 = "=RAND()"
.Range("C1").AutoFill Destination:=.Range("C1:C20"), _
Type:=xlFillDefault
End With
Sheets("Sheet5").Range("C1:C20").Formula = "=RAND()"
Range("B2:B14").Select
With Selection.Font
.Name = "Arial"
.FontStyle = "Bold"
.Size = 10
.Strikethrough = False
.Superscript = False
.Subscript = False
.OutlineFont = False
.Shadow = False
.Underline = xlNone
.ColorIndex = xlAutomatic
End With
Range("B2:B14").Font.FontStyle = "Bold"
You can also experiment with the macro recorder by recording the
same operation performed different ways in the user interface.
For example, if you format a range by using the Bold button
on the Standard toolbar, the macro recorder uses the Bold
property.
Range("B2:B14").Font.Bold = True
Dim wb As Excel.Workbook
Dim dc As Word.Document, cb As MSForms.CommandButton
Remember to set the ScreenUpdating property back to True
when your macro finishes running (older versions of Microsoft
Excel automatically reset this property, but Microsoft Excel 97
and Word 97 don't).
Application.ScreenUpdating = False
Strategies for Optimizing Microsoft Excel
Using Worksheet Functions
A Microsoft Excel worksheet function that operates on a range
of cells is usually faster than a Visual Basic macro that accomplishes
the same task. For example, the SUM worksheet function is much
faster than Visual Basic code that iterates a range and adds the
values in the range's cells. For example, the following code runs
relatively slowly.
The following code runs faster than the preceding example.
Using SpecialPurpose Visual Basic
Methods
There are also several specialpurpose Visual Basic methods
that offer a concise way to perform a specific operation on a
range of cells. Like worksheet functions, these specialized methods
are faster than the generalpurpose Visual Basic code that
accomplishes the same task.
The following example changes the value in each cell in the range
A1:A200 in a relatively slow way.
The following example, which uses the Replace method, performs
the same operation much faster.
The following example shows a relatively slow way to add a blue
oval to each cell in the range A1:A500 that contains the value
4.
The following example, which uses the Find and FindNext
methods, performs the same task much faster.
For Each c In Worksheets(1).Range("A1:A200")
totVal = totVal + c.Value
Next
Function that produce aggregrate results (such as PRODUCT, COUNT,
COUNTA, and COUNTIF) are good candidates for replacing slower
Visual Basic code, as are worksheet functions (such as MATCH and
LOOKUP) that can take a range as an argument.
totVal = Application.WorksheetFunction.Sum(Worksheets(1).Range("a1:a200"))
For Each c In Worksheets(1).Range("a1:a200").Cells
If c.Value = 4 Then c.Value = 4.5
Next
Worksheets(1).Range("a1:a200").Replace "4", "4.5"
For Each c In Worksheets(1).Range("a1:a500").Cells
If c.Value = 4 Then
With Worksheets(1).Ovals.Add(c.Left, c.Top, c.Width, c.Height)
.Interior.Pattern = xlNone
.Border.ColorIndex = 5
End With
End If
Next
With Worksheets(1).Range("a1:a500")
Set c = .Find(4)
If Not c Is Nothing Then
firstAddress = c.Address
Do
With Worksheets(1).Ovals.Add(c.Left, c.Top, _
c.Width, c.Height)
.Interior.Pattern = xlNone
.Border.ColorIndex = 5
End With
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
In addition to the general information discussed in this chapter, you can use the following techniques to create smaller and faster macros in Word.
Using Range Objects
Working with Range objects is faster than working with
the Selection object. You can define and use multiple Range
objects, which are invisible to the user.
Using Next and Previous
Whenever possible, use Next and Previous to return
the next or previous item in a collection. For example, using
myRange.Next Unit:=wdWord
is faster than indexing the collection of words (myRange.Words(10)).
Avoiding Using the WordBasic Object
Methods of the WordBasic object are slower than methods
and properties of other Visual Basic objects. For example, WordBasic.FileOpen
is slower than Documents.Open.
Executing Builtin Dialog Boxes
A With statement is an efficient way to set many properties
of a single object. Another technique for setting multiple properties
is to set the properties of a builtin dialog box and then
execute the dialog box. Executing a builtin dialog box is
faster than using the With statement because the builtin
dialog box stores the property values and then sets them all at
once (using the Execute method), whereas the With
statement sets properties one at a time. The following example
sets a number of paragraph formatting properties by using a With
statement.
The following example sets the same properties as the preceding
example, but runs faster because it executes a builtin dialog
box.
With Selection.ParagraphFormat
.Alignment = wdAlignParagraphCenter
.KeepWithNext = True
.LeftIndent = InchesToPoints(0.5)
End With
Set dlg = Dialogs(wdDialogFormatParagraph)
dlg.Alignment = wdAlignParagraphCenter
dlg.KeepWithNext = True
dlg.LeftIndent = "0.5"
dlg.Execute