When writing VBA macros in Word, you might notice that the Range object and the Selection object return different start and end positions for what appears to be the same text. This discrepancy causes macro errors, incorrect formatting, and unexpected behavior in automated tasks. The root cause lies in how Range and Selection track content: Range uses story-level character positions, while Selection uses the current viewport and collapsed boundaries. This article explains why the bounds differ, demonstrates how to inspect both objects, and provides reliable methods to align them for consistent macro execution.
Key Takeaways: Understanding Range and Selection Position Differences
- Range.Start and Range.End use story-relative character counts: These values include all text in the main story, including headers, footers, and text boxes.
- Selection.Start and Selection.End use document-relative character counts: These values refer only to the visible document body and exclude non-main story elements.
- Use Range.DocumentRange to get consistent positions across all stories: This method returns a Range object that covers the entire document, making comparison with Selection reliable.
How Word VBA Defines Range vs Selection
The Range object in Word VBA represents a contiguous area of a document, defined by a start and end character position. These positions are measured from the beginning of the story that contains the range. A story is a part of the document that can contain text independently, such as the main body, headers, footers, footnotes, endnotes, or text boxes. Each story has its own zero-based character index. For example, the main body story starts at position 0, but a header story also starts at position 0 within that story. When you create a Range object using ActiveDocument.Range(Start, End), the positions are relative to the main body story only.
The Selection object, on the other hand, represents the currently selected area in the active window. Its Start and End properties return positions relative to the entire document, but only for the main body story. Selection does not include text from headers, footers, or other stories unless those stories are specifically activated. Additionally, Selection can exist in a collapsed state (no text selected) where Start equals End. This collapsed state is common after using commands like Selection.Collapse or after inserting text via Selection.TypeText.
The key difference causing bound discrepancies is the scope of the position reference. Range.Start and Range.End can point to any story, while Selection.Start and Selection.End always point to the main body story. If your Range object points to a header story, its positions will be completely different from Selection positions that point to the main body, even if the visible text appears similar.
Why Range and Selection Positions Differ for the Same Text
Consider a document with a header containing the word “Draft” and a body containing “This is the main content.” If you run a macro that sets a Range to the header story, Range.Start might return 0 and Range.End might return 5 (the length of “Draft”). Meanwhile, Selection.Start might return 0 and Selection.End might return 23 for the body text. Both objects refer to different stories, so their positions are not comparable. Even if you select the header text manually, Selection will still report positions relative to the main body story, not the header story. This happens because Selection always operates in the main body story unless you explicitly activate a different story using Selection.StoryType.
Another cause is the collapsed state of Selection. After certain operations like Selection.Find.Execute, the Selection may collapse to the start of the found text. In that case, Selection.Start equals Selection.End, but the Range object created from the same find operation might retain the full found range. This leads to mismatches when comparing bounds in code.
Steps to Inspect and Align Range and Selection Bounds
Method 1: Use DocumentRange for Consistent Reference
- Open the VBA Editor
Press Alt+F11 to open the VBA editor in Word. Insert a new module from the Insert menu. - Write a macro to display both object bounds
Paste the following code:Sub CompareBounds()
Dim rng As Range
Set rng = ActiveDocument.Range
MsgBox "Range Start: " & rng.Start & vbCrLf & "Range End: " & rng.End
MsgBox "Selection Start: " & Selection.Start & vbCrLf & "Selection End: " & Selection.End
End Sub - Run the macro with different selections
Select text in the body, then run the macro. Note the values. Now select text in a header (double-click the header area) and run the macro again. The Range object will show positions for the entire document, while Selection will show positions relative to the main body only. - Use DocumentRange to get the full document
Modify the macro to useActiveDocument.Range(which returns the main body story) and compare with Selection. For a consistent reference that includes all stories, useActiveDocument.StoryRangesto iterate through each story.
Method 2: Convert Selection to Range for Direct Comparison
- Create a Range from the current Selection
UseSet rng = Selection.Rangeto create a Range object that mirrors the Selection. This Range will have the same Start and End as the Selection, but it is a separate object that you can manipulate without affecting the selection. - Compare the Range with another Range
If you have a Range from a different story, you cannot compare positions directly. Instead, compare the text or use theInRangemethod:If rng1.InRange(rng2) Then. - Avoid relying on Start and End for cross-story comparisons
Instead of comparing numeric positions, use theRange.Textproperty to check content equality, or useRange.Documentto access the parent document and then use document-level methods.
Method 3: Force Selection to a Specific Story
- Activate the desired story
UseActiveDocument.StoryRanges(wdPrimaryHeaderStory).Selectto set the selection to the primary header story. After this, Selection.Start and Selection.End will reflect positions within that story. - Check the StoryType property
After activating, verify withSelection.StoryTypeto ensure you are in the correct story. This helps debug why bounds differ. - Reset to main body when done
UseActiveDocument.Range(0, 0).Selectto return Selection to the start of the main body story. This prevents subsequent code from operating in the wrong story.
Common Issues When Range and Selection Bounds Differ
Range.Start Returns a Value Larger Than the Document Length
If your Range object points to a story other than the main body, its Start and End values may exceed the length of the main document. For example, a Range in a footer story might have Start=0 and End=50, but the main document might only have 200 characters. To fix this, always check the Range.StoryType property before using Start or End for calculations. If you need to work with the main body, use ActiveDocument.Range explicitly.
Selection.End Is Less Than Range.End After a Find Operation
After a Selection.Find.Execute, the Selection often collapses to the start of the found text, making Selection.End equal to Selection.Start. The Range object created from Selection.Range before the find retains the original bounds. To preserve the found range, assign the found range to a Range variable immediately after the find: Set rng = Selection.Range. Then use rng.End to get the full end position.
Range.Start and Selection.Start Are Both Zero but Text Is Different
This happens when the Range is at the start of a non-main story (like a header) and the Selection is at the start of the main body. Both return 0, but they refer to different stories. To differentiate, check the Range.StoryType and Selection.StoryType. If they differ, you are comparing positions from different stories. Use the Range.DocumentRange method to get a Range that spans the entire document for a unified reference.
| Item | Range Object | Selection Object |
|---|---|---|
| Position reference | Relative to the story containing the range | Relative to the main body story only |
| Can span multiple stories | Yes, via StoryRanges collection | No, always within one story at a time |
| Collapsed state possible | Yes, Start equals End | Yes, after Collapse or TypeText |
| Affected by viewport | No, independent of window | Yes, depends on active window and selection |
| Best for cross-story operations | Yes, use StoryRanges | No, must activate each story separately |
Now you can identify why Range and Selection return different bounds and adjust your VBA code accordingly. Use the StoryRanges collection to iterate through all stories when you need document-wide coverage. For direct comparisons, convert Selection to a Range object and use methods like InRange instead of comparing numeric positions. As an advanced tip, use ActiveDocument.StoryRanges(wdMainTextStory) to always get the main body story explicitly, avoiding accidental cross-story errors in your macros.