Hi Daiya:
Oh dear: Daiya's into VBA now -- no place is now safe...
OK:
Sub DeleteBookmarks()
'
Dim i As Long
The "Dim" statement is a throwback to the original Basic language. It means
"Dimension an area of memory to hold this thing." In VBA you do not have to
DIM anything, the compiler will automatically instantiate any variables it
needs when it runs.
However, if you set the line "Option Explicit" at the top of your module,
the compiler will then insist that you DIM everything. This is a VERY
useful protection if you are a bad typist inclined to misspell your variable
names, like I am.
OK, it's difficult to misspell the variable name "i". The rest of that
statement "as long" "types" the variable. It defines the kind of content it
can contain (which also, in VBA, defines the size of the thing in memory).
In this case, you are defining i to be a "long integer". From the Help: "
Long (long integer) variables are stored as signed 32-bit (4-byte) numbers
ranging in value from -2,147,483,648 to 2,147,483,647. The type-declaration
character for Long is the ampersand (&)."
While we know that no document can possibly contain two million bookmarks,
and we could assume that a simple "integer" type would do us (and save two
bytes of memory) it is useful to know that Word VBA is a 32 bit machine.
Thus a "long" is its native word size. In this case, the variable will be
read and written on every iteration, and Word handles long variables much
much faster than integers. It has to do extra processing to write data to
and from a "half word". So in VBA always use a long unless you have a very
good reason to use something else: two bytes extra memory will not kill you,
but slow macros will.
ActiveDocument.Bookmarks.ShowHidden = True
That statement is not necessary in this particular macro. There are two
kinds of bookmarks: those whose names begin with an underscore, which are
hidden from normal display, and others which are visible in the document if
you turn on Show Bookmarks. The statement makes the hidden ones visible.
The statement itself is a classic VBA "dot delimited" statement, where the
"dot operator" means "within the foregoing". ActiveDocument is a "property"
that provides a shorthand way of saying "The document the cursor is in" (the
one that has focus). Word can have twenty-odd documents "open", but only
one at a time can be "active". Since you have to write several lines of
code to explicitly retrieve the name of the document you want to work in,
then store it, most people use "ActiveDocument" where speed doesn't matter.
In this case, you only want to do it once, so if it takes Word a millisecond
to figure out what "ActiveDocument" means, who cares? But if this statement
was inside the loop, you would Dim and Set a variable to store a reference
to the active document in memory. This would be a thousand times faster.
ActiveDocument is actually a property of "Application", but nobody bothers
to write "Application.ActiveDocument" because if you are running a macro,
you must be running it within Word, and so ActiveDocument is explicit in
itself.
This next statement is the only one Daiya actually didn't understand. It's
a particularly convoluted construct due to the peculiarities of the
bookmarks collection.
Normally, you would code it as:
For (each of) TheBookmarks in (the) ActiveDocument
.delete
Next
The For...Each...Next statement is one of the most stunningly powerful and
useful "nice to have's" in VBA. Behind the scenes, VBA adds a whole lot of
code to define a variable, load it with the number of "things" in whatever
collection you use it with, then work down the collection from 1 to last,
doing whatever (in this case, deleting) to each thing as it comes to it.
However, in the case of a physical object such as a bookmark or a hyperlink,
if you delete it, Word instantly renumbers them all. Since the variable
(TheBookmarks in the example) is actually simply an Item number, the
sequence would be this:
First Word would find bookmark number 1 and delete it. Then it would
increment TheBookmarks from "1" to "2). Then it would shuffle the bookmarks
collection and renumber them all. It would then delete bookmark "2". See
the problem? Word has renumbered the bookmarks. The "next" bookmark is now
Item number "1" in the collection. But you are deleting number "2", which
used to be "3". If you run this loop "forwards", you end up deleting every
SECOND object.
To handle this, Daiyo has coded the loop to run BACKWARDS, deleting each one
from the BOTTOM of the document to the top. And to do that, she had to do
it the hard way. She had to load her own variable: "For i =
activedocument.bookmarks.count" means "Let i become the count of all the
bookmarks in the document". The rest of the line: "To 1 Step -1" causes
Word to decrement the variable instead of incrementing it. Usually in a
For...Each...Next you do not need to specify the "Step" parameter, because
the default is +1, which means the variable increases by 1 on each run
through the loop. In this case, you have to specify Step because you want
the increment to be "-1", which subtracts 1 from the variable "i" on each
pass.
The next but of the statement deletes the bookmark.
"ActiveDocument.Bookmarks" says "Of all the bookmarks in the active
document, choose the one indicated by the value of "i" and delete it.
The last part of the statement is "next i", which simply tells the compiler
where the statements embedded in the loop end.
For i = ActiveDocument.Bookmarks.Count To 1 Step -1
ActiveDocument.Bookmarks(i).Delete
Next i
Had we wanted to take a purists approach, we could have coded this as:
With ActiveDocument.Bookmarks
for i = .count to 1 step -1
.item(i).delete
next ' i
End with
And in the process, we would have improved the speed of the macro by more
than a thousand times. And made the code completely incomprehensible, and
thus unmaintainable. When coding VBA, performance is rarely a
consideration. But maintainability is ALWAYS paramount. Particularly if it
is YOU who is going to have to figure out what the HELL this thing does in a
year's time
The reason the second version is faster is because because of a technique
called "early binding". Word only has to figure out where the bookmarks are
in the active document once, in the second example, instead of on each pass
through the loop. Similarly, the ' character on the last line turns
everything following it into a comment. In this case, that tells Word that
the For... Each... Next statement ends on this line, but prevents Word from
having to evaluate the variable "i". If you leave out the apostrophe, Word
will first find i then open it and move its content into memory, before
discovering that it doesn't need to know what is in i at that point. It
wastes about 300 machine cycles on each look.
On the other hand, if you do not put "i" on the last line, and you start
nesting For... Each... Next statements vigorously, in a year's time you will
find it almost impossible to figure out which loop ends at that point. Word
doesn't need to evaluate "i" until it gets to the top of the loop, when it
can do so with a simple bit-wise operation. It doesn't need to know "how
much" is left in the variable, it needs only find out if there is "any"
left. It can do that in four machine cycles compared with 300.
Similarly, with the first example Word has to evaluate the entire collection
of bookmarks twice on each run through, once to figure out how many there
are, and again to find the one to delete. This will burn maybe a million
CPU cycles on each pass.
But faced with the choice of writing it the first way and being able to
figure it out in a year's time, or producing a macro that takes two seconds
to run (on a large document) versus 500 microseconds, I would write it the
first way, every time. MY time and effort is worth a hell of a lot more to
me than the computer's
So there: for anyone who really wanted to know, that's how it works! The
macro Daiyo offered here is one of the more intricate ones you will see in
your travels: she has done a lot with a few lines of VBA!
Hope you enjoyed it.
Update:
Bookmarks just randomly showed up in my Word 2004 doc, when I copied text
from it, and pasted the text into Safari, then went back to my Word doc,
suddenly I had bookmarks. Note that I had not pasted anything into Word.
This is the first time I have noticed this behavior since turning on show
bookmarks several days ago, but I have not been using Word very intensively.
Here is a macro to delete bookmarks (maybe. I edited it from a more complex
one that I didn't really understand, so I don't totally understand this one
either, but it just worked in my single test). Tony, you might want to test
it on a copy.
Go to Tools | Macros, Macros.... In the Dialog box, type DeleteBookmarks,
then click Create, which flips you into Visual Basic. Paste the above text
in where the cursor is (hope for no error messages). Word | Close and Return
to Word. In a copy of a doc with a lot of bookmarks, go to Tools | Macro,
Macros, select DeleteBookmarks, and click Run. See if the bookmarks go
away. Mess with the doc to see if it seems to crash less with no bookmarks.
Let us know whether that has any effect.
DM
--
Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.
John McGhie <
[email protected]>
Consultant Technical Writer
Sydney, Australia +61 4 1209 1410