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