Why Word VBA Range Object Returns Different Bounds Than Selection
🔍 WiseChecker

Why Word VBA Range Object Returns Different Bounds Than Selection

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.

ADVERTISEMENT

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

  1. Open the VBA Editor
    Press Alt+F11 to open the VBA editor in Word. Insert a new module from the Insert menu.
  2. 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
  3. 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.
  4. Use DocumentRange to get the full document
    Modify the macro to use ActiveDocument.Range (which returns the main body story) and compare with Selection. For a consistent reference that includes all stories, use ActiveDocument.StoryRanges to iterate through each story.

Method 2: Convert Selection to Range for Direct Comparison

  1. Create a Range from the current Selection
    Use Set rng = Selection.Range to 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.
  2. 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 the InRange method: If rng1.InRange(rng2) Then.
  3. Avoid relying on Start and End for cross-story comparisons
    Instead of comparing numeric positions, use the Range.Text property to check content equality, or use Range.Document to access the parent document and then use document-level methods.

Method 3: Force Selection to a Specific Story

  1. Activate the desired story
    Use ActiveDocument.StoryRanges(wdPrimaryHeaderStory).Select to set the selection to the primary header story. After this, Selection.Start and Selection.End will reflect positions within that story.
  2. Check the StoryType property
    After activating, verify with Selection.StoryType to ensure you are in the correct story. This helps debug why bounds differ.
  3. Reset to main body when done
    Use ActiveDocument.Range(0, 0).Select to return Selection to the start of the main body story. This prevents subsequent code from operating in the wrong story.

ADVERTISEMENT

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.

ADVERTISEMENT