VBA Code
link - https://gregmaxey.com/word_tip_pages/ repeating_data.html
Fields Collection
This accessing the collection of all the fields in a document
Set objFields = objDocument.Fields
ActiveDocument.Fields(1).Code.Text = "DATE \* MERGEFORMAT"
ActiveDocument.Fields(1).Result.Text = "07/07/04"
Locking Fields
Locks all the fields in the collection
objFields.Locked = True
If a field is locked it can no longer be updated
objField.Locked = True / False
Sequence Numbering Fields with sub levels
'level 1
Selection.Fields.Add Range:=Selection.Range
Type:=wdFieldType.wdFieldEmpty
Text:="SEQ \L1 \*arabic \c \* MERGEFORMAT"
PreserveFormatting:=True
'level 2
Text:="SEQ \L2 \*alphabetic \c \* MERGEFORMAT"
ActiveDocument.TablesOfContents(1).UpdatePageNumbers
ActiveDocument.TableOfContents.Update
Set objRange = objField.Code
Set objRange = objField.Result
Displays the field codes
objField.ShowCodes = True / False
objField.Type = wdFieldType.wdFieldType
objField.Update
objField.Delete
objField.Code.Text
objField.Result.Text
objField.DoClick
objField.Kind = wdFieldKind.wdFiledKindNone
Selection.GoTo What:=wdGoToItem.wdGoToField, _
Which:=wdGoToDirection.wdGoToNext, _
Count:=1 , _
Name:="Fieldname"
ActiveWindow.View.ShowFieldCodes = True / False
Unlinking Fields
Dim objField As Field
For Each objField In ActiveDocument.Fields
objField.Unlink
Next objField
Breaking Fields
Dim objField As Field
For Each objField In ActiveDocument.Fields
objField.LinkFormat.BreakLink
Next objField
Inserting Sequential Numbering
Selection.Fields.Add Range:=Selection.Range,
Type:=wdFieldEmpty,
Text:="SEQ name \n",
PreserveFormatting:=True
Selection.Fields.Add Range:=Selection.Range,
Type:=wdFieldEmpty,
Text:="DATE \@ "dd-mmm-yyyy",
PreserveFormatting:=True
'adding the date
Selection.Collapse Direction:=wdCollapseDirection.wdcollapsestart
Set objField = ActiveDocument.Fields.Add(Range:=Selection.Range, _
Type:=wdFieldType.wdfFeldDate)
'update in header and footers
Dim objSection As Section
For Each objSection In ActiveDocument.Sections
sctSection.Headers(wdHeaderFooterIndex.wdHeaderFooterPrimary).Range.Fields.Update
sctSection.Footers(wdHeaderFooterIndex.wdHeaderFooterPrimary).Range.Fields.Update
Next sctSection
VBA - Inserting Fields
ActiveDocument.Fields.Add Range:=Selection.Range, _
Type:=wdFieldType.wdFieldEmpty, _
Text:="SEQ name \n", _
PreserveFormatting:=True
ActiveDocument.Fields.Add Range:=Selection.Range, _
Type:=wdFieldType.wdFieldEmpty, _
Text:="SEQ L1 \r0 \h", _
PreserveFormatting:=True
ActiveDocument.Fields.Add Range:=Selection.Range, _
Type:=wdFieldType.wdFieldEmpty, _
Text:="REF BookmarkName", _
PreserveFormatting:=False
ActiveDocument.Fields.Add Range:=Selection.Range, _
Type:=wdFieldType.wdFieldEmpty, _
Text:="PAGE", _
PreserveFormatting:=False
StyleRef
ActiveDocument.Fields.Add Range:=Selection.Range, _
Type:=wdFieldType.wdFieldEmpty, _
Text:="STYLEREF ""Heading 1"" \* MERGEFORMAT", _
PreserveFormatting:=False
ActiveDocument.Fields.Add Range:=Selection.Range, _
Type:=wdFieldType.wdFieldStyleRef, _
Text:="""Heading 1"" \* MERGEFORMAT", _
PreserveFormatting:=False
Inserting nested fields using VBA
It is usually best to use ranges in VBA code, and avoid using selections. However, VBA does not allow you to create nested fields "on the fly" using ranges.
The best workaround is to store your frequently used nested fields as AutoText entries in an Add-in. The you can insert the Autotext entry using ranges:
Sub InsertNestedPageFieldInHeader()
Dim MyRange As Range
Set MyRange = ActiveDocument.Sections(1) _
.Headers(wdHeaderFooterPrimary).Range
MyRange.Collapse wdCollapseEnd
Templates(Options.DefaultFilePath(wdStartupPath) & "\MainAddin.dot") _
.AutoTextEntries("PagePlus1").Insert _
Where:=MyRange, RichText:=True
End Sub
Sub InsertHyperLinkFieldWithinMacroButtonField()
ActiveWindow.View.ShowFieldCodes = True
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, Text:= _
"HYPERLINK ""https://wordmvp.com/""", PreserveFormatting:=False
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.InsertAfter "MacroButton ""FollowLink"""
ActiveWindow.View.ShowFieldCodes = False
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Update
End Sub
Using selections in Headers or Footers is a complete no-no.
So if you need to create a nested Headers or Footer field "on the fly", create the field in the main document, and cut and paste it into the Headers or Footer:
Sub InsertNestedPageFieldInHeader()
Dim MyRange As Range
ActiveWindow.View.ShowFieldCodes = True
'Insert dummy para at end of document
ActiveDocument.Range.InsertAfter vbCr
Set MyRange = ActiveDocument.Range
MyRange.Collapse wdCollapseEnd
MyRange.Select
'Insert nested field
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, Text:= _
"PAGE ", PreserveFormatting:=False
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
PreserveFormatting:=False
Selection.TypeText Text:="= "
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.MoveRight Unit:=wdCharacter, Count:=1
Selection.TypeText Text:=" + 1"
Selection.MoveRight Unit:=wdCharacter, Count:=2
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Update
ActiveWindow.View.ShowFieldCodes = False
'Cut field, delete dummy para mark, and paste field into header
Selection.Cut
ActiveDocument.Paragraphs.Last.Range.Delete
Set MyRange = ActiveDocument.Sections(1) _
.Headers(wdHeaderFooterPrimary).Range
MyRange.Collapse wdCollapseEnd
MyRange.Paste
End Sub
VBA - Updating Fields
Updates all the fields in the collection
objFields.Update
If a field is locked it can no longer be updated.
This example updates any fields in the active document that aren't updated automatically.
Dim objField As Field
For Each objField In ActiveDocument.Fields
If objField.LinkFormat.AutoUpdate = False Then
objField.LinkFormat.Update
End If
Next afield
In Header and Footer
Dim objHeader As HeaderFooter
Dim objField As Field
Set objHeader = Application.ActiveDocument.Sections(1).Headers(wdHeaderFooterIndex.wdHeaderFooterFirstPage)
For Each objField In objHeader.Range.Fields
objField.Update
Next objField
Inside Text Boxes
'ActiveDocument.Shapes(1).Name = "TB_HeaderLinking_DocReportTitle"
ActiveDocument.Shapes.Item("TB_HeaderLinking_DocReportTitle").TextFrame.TextRange.Fields.Update
VBA - Inserting Graphics
This example inserts a graphic as an inline shape (using an INCLUDEPICTURE field) and then displays the shape's field code.
Dim iShapeNew As InlineShape
Set iShapeNew = ActiveDocument.InlineShapes.AddPicture(FileName:="C:\Windows\Tiles.bmp", _
LinkToFile:=True, _
SaveWithDocument:=False, _
Range:=Selection.Range)
Call MsgBox (iShapeNew.Field.Code.Text)
VBA - Inserting Nested Fields
It is usually best to use ranges in VBA code, and avoid using selections. However, VBA does not allow you to create nested fields "on the fly" using ranges.
The best workaround is to store your frequently used nested fields as AutoText entries in an Add-in. The you can insert the Autotext entry using ranges:
Public Sub InsertNestedPageFieldInHeader()
Dim objRange As Range
Dim sfolderpath As String
Set objRange = ActiveDocument.Sections(1).Headers(wdHeaderFooterPrimary).Range
objRange.Collapse wdCollapseEnd
sfolderpath = Application.Options.DefaultFilePath(wdStartupPath) & "\TemplateName.dot"
Application.Templates(sfolderpath).AutoTextEntries("PagePlus1").Insert Where:=objRange, _
RichText:=True
End Sub
However, if you really need to create the field dynamically you will have to use selections instead of ranges.
Public Sub InsertHyperLinkFieldWithinMacroButtonField()
ActiveWindow.View.ShowFieldCodes = True
Selection.Fields.Add Range:=Selection.Range,
Type:=wdFieldType.wdFieldEmpty,
Text:= "HYPERLINK ""http://www.bettersolutions.com""",
PreserveFormatting:=False
Selection.MoveLeft Unit:=wdUnits.wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Add Range:=Selection.Range,
Type:=wdFieldType.wdFieldEmpty, _
PreserveFormatting:=False
Selection.InsertAfter "MacroButton ""FollowLink"""
ActiveWindow.View.ShowFieldCodes = False
Selection.MoveRight Unit:=wdUnits.wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Update
End Sub
You cannot use Selections in the Headers and Footers.
If you need to create a nested Headers or Footer field dynamically then create the field in the main document, and cut and paste it into the Headers or Footer:
Public Sub InsertNestedPageFieldInHeader()
Dim objRange As Range
ActiveWindow.View.ShowFieldCodes = True
'Insert dummy para at end of document
ActiveDocument.Range.InsertAfter vbCr
Set objRange = ActiveDocument.Range
objRange.Collapse wdCollapseDirection.wdCollapseEnd
objRange.Select
'Insert nested field
Selection.Fields.Add Range:=Selection.Range,
Type:=wdFieldEmpty,
Text:= "PAGE ",
PreserveFormatting:=False
Selection.MoveLeft Unit:=wdUnits.wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Add Range:=Selection.Range,
Type:=wdFieldType.wdFieldEmpty, _
PreserveFormatting:=False
Selection.TypeText Text:="= "
Selection.MoveRight Unit:=wdUnits.wdCharacter, Count:=1, Extend:=wdExtend
Selection.MoveRight Unit:=wdUnits.wdCharacter, Count:=1
Selection.TypeText Text:=" + 1"
Selection.MoveRight Unit:=wdUnits.wdCharacter, Count:=2
Selection.MoveLeft Unit:=wdUnits.wdCharacter, Count:=1, Extend:=wdExtend
Selection.Fields.Update
ActiveWindow.View.ShowFieldCodes = False
'Cut field, delete dummy para mark, and paste field into header
Selection.Cut
ActiveDocument.Paragraphs.Last.Range.Delete
Set objRange = ActiveDocument.Sections(1).Headers(wdHeaderFooterIndex.wdHeaderFooterPrimary).Range
objRange.Collapse wdCollapseDirection.wdCollapseEnd
objRange.Paste
End Sub
VBA - Numbered List
Create a numbered list using SEQuence fields
This will apply numbering to a range of paragraphs.
Using Ranges is much faster than using Selections
Sub CreateNumberedList()
Dim objRange As Range
Dim objDummyRange As Range
Dim objSelectedRange As Range
Dim lCounter As Long
Set objSelectedRange = Selection.Range
For lCounter = 1 To objSelectedRange.Paragraphs.Count
Set objRange = objSelectedRange.Paragraphs(lCounter).Range
'check paragrah contains text
If Len(objRange.Text) > 1 Then
'Deletes existing seq field if present
Set objRange = objSelectedRange.Paragraphs(lCounter).Range
If objRange.Characters(1).Fields.Count > 0 Then
If (objRange.Fields(1).Type = wdFieldSequence) Then
objRange.Fields(1).Delete
'deletes the dot and tab as well
objRange.End = objRange.Characters(2).End
objRange.Delete
End If
End If
'insert new sequence field
objRange.Collapse
'first create a dummy range object and move it to the right one,
'so that it will end up being located AFTER the field you're adding.
'This is necessary because when you add a field to a range,
'the range (unlike selection) ends up at the start of the field!!!
'this is one of the unfortunate "features" of ranges
Set objDummyRange = objRange.Duplicate
objDummyRange.Move Unit:=wdUnits.wdCharacter, Count:=1
objRange.Fields.Add Range:=objRange, _
Type:=wdFieldEmpty, _
Text:="SEQ numberedlist\r" & lCounter, _
PreserveFormatting:=True
Set objRange = objDummyRange.Duplicate
objRange.Move Unit:=wdUnits.wdCharacter, Count:=-1
'Set paragraph indents as desired for list
With objRange.ParagraphFormat
.LeftIndent = CentimetersToPoints(1.27)
.FirstLineIndent = CentimetersToPoints(-1.27)
End With
objRange.InsertAfter "." & vbTab
End If
Next lCounter
objSelectedRange.Select
End Sub
VBA - MacroButton
Run a macro when a user double-clicks a button in the document
Press Control+F9 to insert a set of Field Code Braces. { }.
Do not simply type the braces; you must use Control + F9.
Type the following between the braces:
MACROBUTTON MyMacroName Double-Click To Run Macro
(Use the name of an actual macro, instead of MyMacroName)
You should end up with something like this:
{ MACROBUTTON MyMacroName Double-Click To Run Macro }
3
Right-click the area between the braces and select Toggle Field Codes.
If desired, format the macro button to look like a button, using Format Font, Format Paragraph, and Format Borders and Shading
Enable a user to double-click text in a document to change its value
Use a macrobutton field. A macrobutton field is a placeholder that displays a value (such as "Y", "N", or "?") and can run a macro when you double-click it. The macro can do anything you want it to do, including change the displayed value.
Before creating the macrobutton, Create the macro which you want to run when the field is double-clicked. The following macro will exactly fit the needs described above:
Sub SymbolCarousel()
Select Case Selection.Fields(1).Code.Characters(29)
Case "Y"
Selection.Fields(1).Code.Characters(29) = "N"
Case "N"
Selection.Fields(1).Code.Characters(29) = "?"
Case "?"
Selection.Fields(1).Code.Characters(29) = "Y"
Case Else
End Select
End Sub
After creating the SymbolCarousel macro, create the macro button by positioning the cursor where you want the N/Y/? value displayed. Then press Ctrl+F9 to insert a set of field braces { }. (Don't just type the braces. You must insert them using Ctlr+F9.)
Within the braces, type very carefullly the exact character string shown between braces below (but don't type the braces). Be sure to leave a space before the word MACROBUTTON, before SymbolCarousel, and before N. Do NOT put a space after the N.
{ MACROBUTTON SymbolCarousel N }
Right-click the field expression and choose Toggle Field codes.
Double-click and see if the N changes to a ?. Double-click again and see if it changes to a Y. Again and it should change back to N.
The macrobutton field can be used as a text marker within a template, or, as the name implies, it can be used to run a macro.
Using MacroButton fields as a text marker
You can use a MacroButton field that doesn't actually run a macro but simply displays a prompt and allows the user to click on the prompt and type. To do this, insert a field like:
{ MACROBUTTON NoMacro [Click here and type name] }
Change "Click here and type name" to whatever text you require. Press F9 to update the field, which will also display the display text instead of the field code:
See Microsoft's fax templates (which are supplied with Word) for examples of this. Also see:
How to create a template that makes it easy for users to "fill in the blanks", without doing any programming.
Using {MacroButton} fields to run a macro
For this, use a field like this:
{ MACROBUTTON MyMacroName [Double-click here to run macro] }
See: Run a macro when a user double-clicks a button in the document for more details of how to create the field.
Macro button fields can make it very easy to set up fairly sophisticated templates with very little programming effort. For example, see:
Using {Macrobutton} fields to insert information from the Outlook Address Book into documents such as letters.
But they can be useful for all sorts of things - for some more examples, see:
Using hyperlinks in protected forms
Enable a user to double-click text in a document to change its value
Organizing your macros
Also see the checkboxes in the Microsoft Fax templates which are supplied with Word, where the macros associated with the fields insert AutoText entries, one AutoText entry being a MacroButton field containing a checked checkbox symbol, the other being a MacroButton field containing an unchecked one. If you copy those fields, and the macros and AutoText entries associated with them (using the Organiser) into your own templates, you can use them unmodified.
"Passing arguments" to MacroButton fields
Macros assigned to MacroButton fields cannot take arguments. In fact if you want to be semantic, macros cannot take arguments, ever, because a macro is defined as a public subroutine that takes no arguments, which is why subroutines that do take arguments are not shown in the list when you select Tools + Macro + Macros.
However, depending on your situation, you can get round this in a number of ways, the best two (depending on the circumstances) being.
Your macro can read the value from a Custom Document Property, or a Document Variable.
You can insert a Private field within your MacroButton field. The first thing your macro should do is look for the code of the Private field (which by definition will be the second field of the Selection) and read the value that you want to pass
This method is especially good if you have more than one MacroButton field in a single document which you want to call the same macro, but with the macro operating on a different variable in each case.
For example, you could create a nested field as follows:
{ { Private Hello world }Macrobutton TestMacro [Double-click to run macro]}
... which would display:
... and the macro could look like this:
Sub TestMacro()
Dim MyString As String
'Ignore first 9 characters of the private field -
the word 'Private', and the spaces
MyString = Mid$(Selection.Fields(2).Code, 9)
MsgBox MyString
End Sub
Instead of a Private field, you could use an Addin field within your MacroButton field. An Addin field is very similar to a Private field but even more private - see Using Addin Fields.
Note that the order matters; the following works as one would wish it to:
{ Macrobutton TestMacro [Double-click to run macro]{ Addin }}
... but the following makes the MacroButton field's display text invisible:
{ { Addin }Macrobutton TestMacro [Double-click to run macro]}
The macro could look like this:
Sub TestMacro()
Dim MyString As String
MyString = Selection.Fields(2).Data
MsgBox MyString
End Sub
Using {MacroButton} fields to insert information from the Outlook Address Book into documents such as letters
The macrobutton field can be used as a text marker within a template, or, as the name implies, it can be used to run a macro.
For example, you may wish to include addressee details in a Letter template, without having to resort to either using a custom dialog (UserForm1) or a mail merge.
In this instance, insert the following field in the document template at the position the addressee information is to be placed:
{MACROBUTTON InsertAddressFromOutlook [Double-click here to insert address; click to type address.]}
To create the field, either:
Press Ctrl+F9 to position the field boundaries "{}" (don't type them); then type MACROBUTTON followed by the macro's name and the display prompt (you can have spaces within the prompt). Or
Select Insert + Field, and in the dialog, find the MacroButton field. This method is particularly good if you are not used to working with the field in question, because of all the help facilities the dialog gives you (see: Some Tips and "Gotchas" for those who are new to Word). The former method is quicker, though.
Then press F9 to update the field, which will also display the display text instead of the field code:
If the user single-clicks anywhere in the field, then entire field is selected:
(Note that the field shading is an option you can switch on or off under Tools + Options + View).
A single left mouse button click anywhere in the field will select the field, allowing it to be overtyped:
A double left mouse button click will run the macro attached to the field.
The macrobutton field can be used to run any macro e.g. you might wish to extract a name from your Outlook contacts list, for insertion on a template for writing company cheques:
{MACROBUTTON InsertNameFromOutlook [Double-click here to insert name; click to type name]}
... which displays:
You can use the following macros with the above fields:
Public Sub InsertAddressFromOutlook()
Dim strCode As String, strAddress As String
Dim iDoubleCR As Integer
'Set up the formatting codes in strCode
strCode = "<PR_GIVEN_NAME> <PR_SURNAME>" & vbCr & _
"<PR_COMPANY_NAME>" & vbCr & _
"<PR_POSTAL_ADDRESS>" & vbCr
'Display the 'Select Name' dialog, which lets the user choose
'a name from their Outlook address book
strAddress = Application.GetAddress(AddressProperties:=strCode, _
UseAutoText:=False, DisplaySelectDialog:=1, _
RecentAddressesChoice:=True, UpdateRecentAddresses:=True)
'If user cancelled out of 'Select Name' dialog, quit
If strAddress = "" Then Exit Sub
'Eliminate blank paragraphs by looking for two carriage returns in a row
iDoubleCR = InStr(strAddress, vbCr & vbCr)
Do While iDoubleCR <> 0
strAddress = Left(strAddress, iDoubleCR - 1) & _
Mid(strAddress, iDoubleCR + 1)
iDoubleCR = InStr(strAddress, vbCr & vbCr)
Loop
'Strip off final paragraph mark
strAddress = Left(strAddress, Len(strAddress) - 1)
'Insert the modified address at the current insertion point
Selection.Range.Text = strAddress
End Sub
Public Sub InsertNameFromOutlook()
Dim strCode As String, strName As String
'Set up the formatting codes in strCode
strCode = "<PR_DISPLAY_NAME>"
'Display the 'Select Name' dialog, which lets the user choose
'a name from their Outlook address book
strName = Application.GetAddress(AddressProperties:=strCode, _
UseAutoText:=False, DisplaySelectDialog:=1, _
RecentAddressesChoice:=True, UpdateRecentAddresses:=True)
'If user cancelled out of 'Select Name' dialog, quit
If strName = "" Then Exit Sub
'Insert the name at the current insertion point
Selection.Range.Text = strName
End Sub
Notes
The user may find that the first time they run the macro in each Word session, they might see the "Choose profile" dialog and have to click OK:
And/or, they may find that the "Select Names" dialog displayed by the macro doesn't automatically display their contacts, and they have to select the drop-down and select "Contacts" every time.
If your users experience either of these problems, it is down to an Outlook configuration issue, and you should post the details in the microsoft.public.outlook.contacts newsgroup.
If the document is protected (using Tools + protect Document), and if the MacroButton foeld is in a protected area of the document, you will need to unprotect the document immediately before the line that starts:
Selection.Range.Text =
and reprotect it immediately after that line - e.g.:
ActiveDocument.Unprotect
Selection.Range.Text = strAddress
ActiveDocument.Unprotect
'Insert the modified address at the current insertion point
Selection.Range.Text = strAddress
ActiveDocument.Protect Type:=wdAllowOnlyFormFields, _
NoReset:=True
Alternatively, if you did want to use a UserForm in an AutoNew macro, and wanted the UserForm to have a button that allowed your users to pick a name from the Outlook Address Book, you could use almost exactly the same code as provided in this article to do that as well. That is essentially how the Microsoft Letter Wizard works.
VBA - Hyperlinks
You must have the (Tools > Options)(View tab, "ScreenTips") option switched on.
The screentips will appear in all the views when you hover over the object.
ActiveDocument.Hyperlinks.Add Anchor:=selection.range, Address:=newdocP & "\" & newdoc, Subaddress:+ """
Anchor - The text or graphic that you want to link to
Address - The address of the link
SubAddress - The name of the location within the destination file (such as bookmark, named range, slide number)
ScreenTip - The text that appears when you hover over with the mouse.
TextToDisplay - The text of the specified hyperlink. This replaces the text or graphic specified by Anchor.
Target - The name of the window or frame you want to load the hyperlink in.
ActiveDocument.Hyperlinks.Add Anchor:=Selection.Range, _
Address:="http://www.bettersolutions.com"
ActiveDocument.Hyperlinks.Add Anchor:= Selection.Range, _
Address:="C:\temp\mydocument.docx"
SubAddress:="mybookmark"
ActiveDocument.Hyperlinks.Add Anchor:= Selection.ShapeRange(1), _
Address:="http://www.bettersolutions.com"
ActiveDocument.Hyperlinks.Add Anchor:= Selection.InlineShapes(1), _
Address:="http://www.bettersolutions.com"
Using hyperlinks in protected forms
If you need to have a clickable hyperlink in a protected area of a document that has been protected for forms, then you have to nest your Hyperlink field within a MacroButton field, and have the MacroButton field call a macro. A single macro can be used for every hyperlink.
The macro you need is as follows:
Sub FollowLink()
Selection.Hyperlinks(1).Follow
End Sub
Although see the warning below).
Having created your macro, insert your hyperlink in the form while it's unprotected, and press Alt+F9 to display the field codes. You'll see something like:
{ HYPERLINK "http://www.bettersolutions.com/word" }
Select the field, and press Ctrl+F9, which will nest the hyperlink field inside another field, as follows:
{ { HYPERLINK "http://www.bettersolutions.com/word" } }
Then edit the field as follows:
{ MacroButton "FollowLink"{ HYPERLINK "http://www.bettersolutions.com/word" }}
Note that, if you don't want the field result to display a space after the url, you need to remove any spaces before and after the nested field, as shown above,
Press Alt+F9 again to turn the display of field codes off, update the field (F9), protect your form and you're done.
Double-click or single-click
However, by default, the user will need to double-click the MacroButton field to follow the hyperlink, rather than being able to single-click it. You can change that behaviour by putting the following code into the AutoExec macro of a Global template
Sub AutoExec
Options.ButtonFieldClicks = 1
End Sub
But doing that will change the behaviour of all MacroButton fields in all documents (they will always be activated by a single-click). If you want the MacroButton fields in documents based on certain templates to be activated by a single-click, and in other documents to be activated by a double-click, you would have to use a DocumentChange event procedure:
Private Sub oApp_DocumentChange()
If ActiveDocument.AttachedTemplate.Name = _
"MyTemplate.Dot" Then
Options.ButtonFieldClicks = 1
Else
Options.ButtonFieldClicks = 2
End If
End Sub
Warning!
A general point about hyperlinking to web pages from Word: be aware that Word doesn't check whether you're online before trying to follow a hyperlink; and if you're not connected to the internet, it doesn't give you the option to dial up. Instead, it takes forever trying unsuccessfully to follow the hyperlink, before timing out.
This problem is compounded if you nest your Hyperlink field within a MacroButton field; because whereas if you click on a normal hyperlink in Word, you can continue to work while Word attempts to open the hyperlink, if you use a macro to follow a hyperlink, you will be unable to work until Word has either opened the link or timed out. By then many users will have rebooted in despair!
Worse still, it fails even if the web page you are hyperlinking to is in your browser's cache, and even if you have made that page available off-line!! If not a bug, this is certainly a nasty feature and well worth emailing mswish@microsoft.com about.
VBA does not offer any way around this (you can't use VBA to test whether you're connected)). The best you can do is:
Sub FollowLink()
Dim Result As Long
Result = MsgBox("Are you definitely connected to the internet?", _
vbYesNo + vbQuestion)
If Result = vbYes Then
Selection.Hyperlinks(1).Follow
Else
MsgBox "Please try again later", vbInformation
End If
End Sub
VBA - Bookmarks
A big difference between Ranges and Bookmarks, when you insert text at the end of a bookmark Word does not expand the Bookmark to include the new text. However when you insert text at the end of a Range, the Range does expand to include the text.
It is possible to replace the bookmark and keep the enclosing bookmark
If the enclosing bookmark contains a paragraph mark, then the text will be replaced.
Two Different Types
The most important thing to remember when working with bookmarks, is that there are two types:
● Placeholder Bookmarks - If you click somewhere in a document and insert a bookmark it will look like an I beam.
● Enclosing Bookmarks - If you select some text and insert a bookmark it will look like the selected text is enclosed in square brackets.
There are several ways to insert text at (or into) a bookmark.
Which method you use will depend on whether you want to retrieve the text from the bookmark at a later time.
If you want to retrieve the text from a bookmark, then it needs to be an enclosing bookmark.
Displaying the Bookmarks
Always make sure that your bookmarks are visible (Tools > Options)(View tab, "Bookmark").
ActiveWindow.View.ShowBookmarks = True / False
Bookmark Objects
Bookmark objects are kept in the Bookmarks collections for the document.
Set objBookmarks = objDocument.Bookmarks
Set objBookmarks = objSelection.Bookmarks
Set objBookmarks = objRange.Bookmarks
itotal = objBookmarks.Count
Every bookmark has a range associated with it
objBookmark.Select
objBookmark.Name
objBookmark.End
objBookmark.Delete
If objBookmark.Empty = True Then
'the bookmark refers to an insetion point only, ie no text
End if
This is the general location of the bookmark
If objBookmark.StoryType = wdStoryType.wdMainTextStory Then
End If
Placeholder Bookmarks
ActiveDocument.Bookmarks("Bookmark_Name").Range.Text "Text"
If the bookmark is a placeholder bookmark then the Text is inserted after the bookmark.
ActiveDocument.Bookmarks("Bookmark_Name").Range.InsertBefore "Text"
ActiveDocument.Bookmarks("Bookmark_Name").Range.InsertAfter "Text"
The text is always inserted after the bookmark regardless of which method you use.
You may want to insert text at a "placeholder" bookmark that can be retrieved at a later time.
Dim objRange As Range
objRange = ActiveDocument.Bookmarks("Bookmark_Name").Range
objRange.Text = "Inserted Text"
ActiveDocument.Bookmarks.Add(Name:="Bookmark_Name", Range:=objRange)
Enclosing Bookmarks
ActiveDocument.Bookmarks("Bookmark_Name").Range.Text "Inserted Text"
If the bookmark is an enclosing bookmark and does not contain any characters then, it will be deleted and the inserted text will appear in its place.
ActiveDocument.Bookmarks("Bookmark_Name").Range.InsertBefore "Inserted Text"
Insert Before - [ Inserted Text Original Text ]
ActiveDocument.Bookmarks("Bookmark_Name").Range.InsertAfter "Inserted Text"
Insert After - [ Original Text ] Inserted Text
You can retrieve the text from an enclosing bookmark using:
sText = ActiveDocument.Bookmarks("Bookmark_Name").Range.Text
You may want to replace the enclosing bookmark with some text (i.e. replacing the bookmark).
What you need to do is replace the bookmark with the text and then recreate the bookmark around the text.
Problem - Inserting Text at a bookmark deletes it !
The following line of code deletes the bookmark
ActiveDocument.Bookmarks("BookmarkName").Range.Text = "Some Text"
Using the InsertAfter or InsertBefore method does not work satisfactorily either.
If the bookmark is currently empty then the following line will leave the bookmark at the start of the text, rather than containing it.
ActiveDocument.Bookmarks("BookmarkName").InsertAfter "Some Text"
And if the bookmark already contains some text then the text is appended to the existing text instead of replacing it.
The best way is to set a range variable to the bookmarks range.
Dim objRange As Range
Set objRange = ActiveDocument.Bookmarks("BookmarkName").Range
objRange.Text = "Some Text"
ActiveDocument.Bookmarks.Add "BookmarkName", objRange
Selecting
ActiveDocument.Bookmarks("bookmark_name").Select
This does not work with bookmarks in headers and footers
Selection.GoTo What:=wdGoToItem.wdGoToBookmark
Which:=wdGoToDirection.wdGoToLast
Count:=1, _
Name:="Bookmark_Name"
The default Count is 1.
The default Which is
When you use the GoTo method with any of the following (wdGoToGrammaticalError, wdGoToProofreadingError or wdGoToSpellingError) constants the range that is returned includes any grammar error text or spelling error text
Creating Bookmarks
The Name must be a single word with no spaces
ActiveDocument.Bookmarks.Add Name:="Bookmark_Name", _
Range:=Selection.Range
If no range is defined then the current insertion point is used
ActiveDocument.Bookmarks.Add Name:="Bookmark_Name"
ActiveDocument.Bookmarks.Exists("Bookmark_Name") = True / False
With ActiveDocument.Bookmarks
.Add Range:=Selection.Range, Name:="Bookmark_Name"
.DefaultSorting = wdBookmarkSortBy.wdSortByName
.ShowHidden = True / False
End With
This creates a bookmark that refers to the same location
objBookmark.Copy (name)
Reassigning Bookmarks
You can add a bookmark to reassign an existing bookmark to a new location.
You do not have to delete the existing bookmark first.
ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="Bookmark_Name"
Deleting Bookmarks
ActiveDocument.Bookmarks("Bookmark_Name").Delete
Inserting Text
If ActiveDocument.Bookmark.Exists("bookmark_name") = True Then
ActiveDocument.Bookmarks("bookmark_name").Select
Selection.TypeText Text:= "Text To Enter"
End If
Changes the contents of a the bookmark
ActiveDocument.Bookmarks("Bookmark_Name").Range.Text = "some text"
ActiveDocument.Bookmarks("Bookmark_Name").Range.InsertAfter "some text"
Got the bookmark, replace the text and keep the bookmark
Selection.GoTo What:=wdGoToItem.wdGoToBookmark, _
Name:="Bookmark_Name"
Selection.Delete Unit:=wdUnits.wdCharacter, Count:=1
Selection.InsertAfter "new text"
ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="Bookmark_Name"
ActiveDocument.Bookmarks("Bookmark_Name").Select
Selection.TypeText Text:="some text"
With oTable.Cell(3, 1)
Set oRange = .Range
oRange.Collapse Direction:=WdCollapseDirection.wdCollapseStart
ActiveDocument.Bookmarks.Add Name:="BM_Name", Range:=oRange
oRange.Style = g_STYLENAME_CLIENTROLE
oRange.Text = "some text"
ActiveDocument.Bookmarks.Add Name:="BM_Name", Range:=oRange
End With
Insert text at a bookmark
ActiveDocument.Bookmarks("myBookmark").Range.Text = "Inserted Text"
If the bookmark is a placeholder bookmark, the inserted text will be inserted.
If the bookmark is an enclosing bookmark, it will be deleted, and the inserted text will appear in it's place.
ActiveDocument.Bookmarks("myBookmark").Range.InsertBefore "Inserted Text"
ActiveDocument.Bookmarks("myBookmark").Range.InsertAfter "Inserted Text"
With both these methods, if the bookmark is a placeholder bookmark, the text will be inserted after the bookmark:
With enclosing bookmarks (even if the bookmark only encloses a space), the following occurs:
InsertAfter - [ Original Text ] Inserted Text
InsertBefore - [ Inserted Text Original Text ]
In order to retrieve the text in a bookmark, the bookmark needs to be an enclosing bookmark.
Then you can use the following to retrieve the text from the bookmark:
strBookmark = ActiveDocument.Bookmarks("myBookmark").Range.Text
You have already seen how to add text to an enclosing bookmark using the InsertBefore method above.
But what if you want to insert text into a placeholder bookmark (making it an enclosing bookmark) so that you can retrieve the text from it at a later time ?
And what if the bookmark is already an enclosing bookmark but you want to replace the text inside it ?
There is no single command in VBA to achieve this.
What you need to do is replace the bookmark with the inserted text (the bookmark is deleted), then re-create the bookmark around the inserted text.
The following code is an example of how this is done:
Dim bmRange As Range
Set bmRange = ActiveDocument.Bookmarks("myBookmark").Range
bmRange.Text = "Inserted Text"
ActiveDocument.Bookmarks.Add Name:="myBookmark", Range:=bmRange
Inserting text at a bookmark without deleting the bookmark
The problem:
The following line:
ActiveDocument.Bookmarks("BookmarkName").Range.Text = "Hello world"
deletes the bookmark.
Using the InsertAfter or InsertBefore method doesn't work satisfactorily either; if the bookmark is currently empty, then the line:
ActiveDocument.Bookmarks("Temp").Range.InsertAfter "Hello world"
This will leave you with the bookmark at the start of the text you've just inserted, rather than containing it. And if the bookmark already contains some text, then "Hello world" will be appended to the existing text, instead of replacing it.
The solution:
The best way to insert text at a bookmark without losing the bookmark - which works reliably whether or not the bookmark currently encloses any text - is to set a range variable to the bookmark's range, as follows:
Dim BMRange As Range
'Identify current Bookmark range and insert text
Set BMRange = ActiveDocument.Bookmarks("MyBookmark").Range
BMRange.Text = "Hello world"
'Re-insert the bookmark
ActiveDocument.Bookmarks.Add "MyBookmark", BMRange
© 2026 Better Solutions Limited. All Rights Reserved. © 2026 Better Solutions Limited TopPrev