Heading

This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
min read

Search for paragraph styles

Word • Macros • Functions
Peter Ronhovde
19
min read

We create a VBA function to find the nearest paragraph with a given style in the interest of encapsulating a bulky set of steps into a concise function we can use in other macros.

Thanks for your interest

This content is part of a paid plan.

Search for Paragraph Styles

While no specific statistics exist for the topic, a list of the lesser used search options when using (Advanced) Find in Microsoft Word probably includes searching for specific paragraph styles (maybe somewhere in the middle). We’ll make use of this feature within VBA to quickly identify nearby paragraphs with a certain style. In upcoming articles, we’ll further apply the function to navigate between inline novel notes as well as other tasks.

Why use Find?

Using the Find property in VBA is a little verbose in terms of the number of lines it requires to set up a clearly defined search.

Why?

Search settings can be in a state which was set based on previous searches. This is not ideal, but the Find operation is super-fast, so we still make use of it when we can. We just need to be more detailed with our search criteria to be safe.

Technically, the current task is simple enough that we could just include the steps in whatever macro we’re creating. The process ends up being longer than it might seems at first, so I prefer to encapsulate the steps into a nice and tidy function.

Function skeleton

We create a function to find the nearest paragraph with a given style. The function skeleton is:

Function FindParagraphStyleRange(SStyle As String, _
Optional BForward As Boolean = True) As Range
' Finds nearest paragraph with a given style SStyle
' Setting BForward to False searches backward

End Function

What parameters?

This function accepts up to two parameters. We could add more search parameters, which I have done for a few of my own search-related functions, but these two are most needed for this use case.

Searched style

We need the paragraph style to search which we call SStyle. A “Style” (with a capital S) is its own object type in VBA meaning it has its own associated properties and methods we can use to do things, but in most cases, it’s easier to refer to a style in VBA using its plain text name like “Normal”.

We can’t use a parameter name of “Style” because VBA reserves that word for its own object, so I added a capital “S” (for a String) to create a unique name for our parameter. The capitalized first letter is a rough naming convention to indicate its value will not be changed inside the function, but I am not always consistent with this notation. Any changes to a string parameter can leak out of the function if we’re not careful, so knowing it won’t change when using the function is helpful.

Search direction

We also allow an optional parameter to specify the direction of the style search. We’ll assume a forward search, so we’ll call the variable BForward. The “B” prefix character indicates a True-False (Boolean) value, but this naming preference isn’t required.

We precede the parameter with the reserved word “Optional” to make it … optional. We don’t need to include it when using the function. We specify a default value of  “= True” since we’re defaulting to a forward search. More specifically, BForward will have a value of True inside the function if a different value is not given when the function is used.

Return type

The function will return a Range type corresponding to the range of a paragraph with the matched style SStyle. If no matching paragraph style is found, it will return Nothing.

Validate given paragraph style

We can’t assume the given style already exists in the current document or template. Some of my validation checks in previous functions are necessary to define a function that doesn’t break easily, but they won’t be used much in practice. Style validation is more important because it’s easy and maybe even common to use the function on a different document where you haven’t transferred the relevant style over.

IsStyle function

We need to check whether the desired paragraph style is valid in the current document or template.

Validating whether a style exists in VBA is problematic since VBA doesn’t give use a standard tool for this necessary purpose. It’s a rather annoying situation, but we created a previous function called IsStyle to accomplish this task. Two examples include:

IsStyle("Some Style")
IsStyle(SStyle)

We can either “pass” a tentative style name to the function as plain text in double quotes or as a name stored in a String variable. The function returns a True or False (Boolean) result depending on whether the style exists in the document or template (usually Normal.dot).

Style conditional statement

We only want to exit the function if the style does not exist, so we reverse the condition with Not (True becomes False and vice versa).

Not IsStyle("Some Style")

As a conditional statement, we roughly get:

If Not IsStyle("Some Style") Then
' Exit function due to invalid paragraph style ...
End If

See this previous article if you want a brief review of conditional statements in VBA.

Exiting the function

We’re creating the FindParagraphStyleRange function, so if we encounter a problem here, we need to set a return value to indicate that result. In general, we set the return value by assigning a value to the function name. In this case, we assign Nothing to indicate we could not find a matching paragraph.

Set FindParagraphStyleRange = Nothing

Then we just exit the function as we’ve done before.

Exit Function

Putting it together, our style validation check is:

' Check whether SStyle is valid in the document
If Not IsStyle(SStyle) Then
' Exit function due to invalid style
Set FindParagraphStyleRange = Nothing
Exit Function
End If

Set a working range

A Find operation requires the Selection or a range variable. As we’ve done many times, we set up a working range variable which we’ll call r just to keep the name short.

Dim r As Range

We want to start our search from the initial position in the document which means we need to set r equal to the Selection’s Range property.

Set r = Selection.Range

We must use Set with ranges (and other objects) because they are not just a value like a number or plain text.

Collapse the range

A user’s initial selection can sometimes create problems for our macros or functions due to unexpected situations. The easiest way to avoid any issues is to just collapse the range, so we know we have a clear starting state for the rest of the macro.

r.Collapse

The range collapses toward the Start position by default. We can’t always do this since we sometimes need the information about the initial selection, but this is a handy trick for our VBA toolbox.

Forward search issue

We have a small issue with forward searches. If we’re already positioned at the beginning of a paragraph with the given style when the Find operation runs, it immediately “finds” the matching paragraph and stops the search … without moving.

It’s like Word says, “Here you go! … I found it real fast. Aren’t you happy?”

No.

If a human (that’s us) runs the search again when they’re already on a paragraph with that style, we assume they already know that fact. We deduce the human wants the next paragraph with that style.

Fortunately, this issue doesn’t occur with backward searches.

Getting the current paragraph range

We need to test whether this is this case, and if so, move to the right, so the Find operation will properly search forward. We haven’t done this in a while, so let’s go through it slowly.

Every well-defined range has a collection of contained Paragraphs which we reference using a dot.

r.Paragraphs

Even partially contained paragraphs will be listed in the collection.

Since our working range r was set earlier to the current document position (perhaps collapsed after that), we can reference the First Paragraph of the collection,

r.Paragraphs.First

A Paragraph is its own kind of object in VBA, so we need the paragraph’s Range property to determine what content it spans in the document.

r.Paragraphs.First.Range

This works even if the range r is empty in the intuitive sense (i.e., the Start and End positions are equal) since we’re asking VBA about the whole first paragraph range.

Checking the current position

In our case, we don’t care about the entire First paragraph range. We just want to know its starting position which is stored in the range’s Start property.

r.Paragraphs.First.Range.Start

The Start property is an actual character position (as in a counting number) in the document as counted from the beginning of the document. We want to know if the Start of our working range r is the same position as the Start of the first paragraph. This uses an equals = condition.

r.Start = r.Paragraphs.First.Range.Start

When used in an If statement, this condition results in a True-False value based on whether the numbers are equal.

I regret VBA equality conditions look like numerical assignments, but their evaluation depends on context (some languages use double equal signs == for conditions to be clearer). If used in isolation, the above statement would instead assign the first paragraph’s Start value to r.Start. It’s an unfortunate ambiguity, but when comparing numbers in conditional statements, context matters.

Avoiding the issue

To get away from the beginning of the initial paragraph, we only need to move a single character to the right to avoid the problem. This is enough to get VBA to not immediately find this paragraph.

r.Move Unit:=wdCharacter

The optional Unit for a character move is from a standard table of VBA constants. The default Count option is 1, so we omitted it.

One might expect we need to move forward to the next paragraph, but a single character is sufficient. Apparently with paragraph style searches, Find is looking for whole paragraphs. It's a little counter-intuitive (to me at least) since the paragraph mark (where formatting information is stored) is at the end of a paragraph, but it works consistently, and I like the "tininess" of the change.

Conditional statement

Our conditional statement looks something like:

If at start of first paragraph and search is forward Then
' Move forward some in the document ...
End If

We’ll use an And operator since both conditions must be True before we adjust the range position.

If r.Start = r.Paragraphs.First.Range.Start And BForward Then
r.Move Unit:=wdCharacter
End If

No style check?

We could add a style check to make sure we’re already on a matching paragraph, but it’s not necessary since it doesn’t improve or change the search result.

Alternative conditional statement

If you don’t like the logic in the above check, an alternative and more direct approach is to compare the first paragraph style to the SStyle parameter. We get the style from the First Paragraph.

r.Paragraphs.First.Style

We do not reference the Paragraph Range this time since a Paragraph has an associated paragraph Style property. The condition is then:

r.Paragraphs.First.Style = SStyle

Here we have a slight contradiction in data types since a Paragraph Style is a Style type with its own properties and methods, but SStyle is a string naming a particular style. However, Word allows this difference in a comparison (or even a style assignment, in general) because people normally specify a style in VBA by a plain text name.

Without any more explanation, a revised conditional statement is:

' Alternative check on forward searches for starting paragraph style (only use one)
If r.Paragraphs.First.Style = SStyle And BForward Then
r.Move Unit:=wdParagraph
End If

We could have still used a wdCharacter move, but the wdParagraph step size is logically more appealing.

Setup Find parameters

A stack of possible search parameters exists, many of which do not apply in this function, so the list below is not exhaustive. However, I prefer to be unambiguous, so I set any relevant search properties just so we don’t encounter any unexpected issues later.

We’re setting up a search using the Find property of our working range r. Each search property or method is referenced from the r.Find property. The long way to list our properties or methods would be something like:

r.Find.ClearFormatting
r.Find.MatchWholeWord = False
r.Find.MatchCase = False
' And so forth ...

It gets long and repetitive, so VBA implements a With statement to make such lengthy lists easier to read.

Using a With statement

A With statement is just a concise way to include references to many methods or properties related to the same object. Inside the With statement, we can omit the object reference—

Huh?

Let's just look at a cardboard example.

Generic With statement explanation

We basically use “With” and give it an object or property to reference inside it.

' Setup a generic With statement
With Some.Object
.PropertyA = Value1 ' Store Value1 in Some.Object.PropertyA
.MethodB ' Run Some.Object.MethodB method
End With

Inside the statement, any property or method that starts with a dot “.” Is assumed to refer back to Some.Object. Then we end it with “End With” like an If statement ends with “End If”. Any line indentation in VBA is just to make it easier to read for humans.

With properties for our Find

More specifically for our current search parameters, we can leave off the r.Find reference for any property or method that needs it.

' Setup search parameters
With r.Find
.ClearFormatting
.MatchWholeWord = False
.MatchCase = False
.MatchSoundsLike = False
.MatchWildcards = False
.MatchPrefix = False
.MatchSuffix = False
.Format = False
.MatchPhrase = False
.IgnoreSpace = False
.IgnorePunct = False
.Highlight = wdUndefined ' Ignore state

' Main options for this search
.Wrap = wdFindStop ' perhaps wdFindContinue?
.Style = SStyle
.Forward = BForward
End With

It’s more organized with fewer dots and such, so it’s easier to read. With statements are also a tiny bit faster than listing them all out separately, but you’d never notice the difference in an editing macro unless it were extremely complex or when processing a very long document.

Some of the options are actually mutually exclusive, but we won’t get into the distinctions in part because we’re searching only for a paragraph style without any specific text.

General parameters

Most parameters are set to reasonable default values with False, meaning I don’t want VBA to make any special accommodations for the search. It would take too long to discuss every search parameter (see the terse descriptions in the official documentation), but let’s briefly address a few of them.

No search wrapping

I usually prefer the .Wrap parameter is set to wdFindContinue (see standard Word VBA table) which means VBA will keep searching from the beginning if we encounter the end of the document (or the reverse for a backward search). However, in this function, we specifically set a search as forward or backward, so it seems more consistent to respect that setting and not wrap a search. The constant for that type of search is wdFindStop.

Search style

The style is the main point of this function. We defined the search style based on the SStyle function parameter, so we use the given style SStyle argument directly as the name provided to the .Style property.

.Style = SStyle

SStyle is literally a plain text name for the intended paragraph style, but VBA will properly interpret the assignment into the Style property since it is actually a Variant (any) type. We could even specify a Style object variable, but using the plain text style name is much more convenient.

Forward search

Similarly, we set the forward search property based on the BForward function parameter.

.Forward = BForward

Remember the BForward parameter was optional, and it defaults to forward with a value of True if it is omitted in a function call.

Execute the search

Now everything is set up, so we can “execute” the search for the Find operation by referencing the Execute method.

r.Find.Execute Replace:=wdReplaceNone

The Replace parameter is optional, and it allows one of three values from the WdReplace table. We don’t want to replace anything since we’re just finding a matching paragraph with the given style, so we use wdReplaceNone. The other options include all or only one replacement.

In fact, many of the parameters listed in the previous With statement can also be given here with their own optional arguments. I like to be clear (the line can get very long), so I prefer to specify most of the parameters in the previous With statement.

Found property

After we execute the search, the Found property of r.Find will be True if the search found a match and False if not.

r.Find.Found ' True or False based on search result

Since it is a Boolean result, we can use it directly in a conditional statement to make a decision.

Set return range

We can set up a conditional statement to assign the returned function value based on whether the search was successful or not.

If search was successful Then
' Do these steps if search was successful ...
Else
' Do these steps if search was not successful ...
End If

Found matching style result

If we match a paragraph with the given style, then we return the whole paragraph range that was found. Fortunately for us, the working range r we used for the search is automatically reset by VBA to the found text, so we can set the return result (via the function name) to the new working range.

Set FindParagraphStyleRange = r

No paragraph style found result

If no matching paragraph with the given style is found, the working range r will not change, but we do not want to return the unchanged original range r. We instead return a result of Nothing to indicate no matching paragraph was found.

Set FindParagraphStyleRange = Nothing

Return result

Together with the Found condition, we return either the found paragraph range or Nothing if the search did not find a paragraph with the given style.

' Check search result and return appropriate value
If r.Find.Found Then
' Found desired paragraph style, so return paragraph range
Set FindParagraphStyleRange = r
Else
' No matching paragraph style was found
Set FindParagraphStyleRange = Nothing
End If

Final Function

Putting it all together, our longer-than-expected paragraph style search function is:

Function FindParagraphStyleRange(SStyle As String, _
Optional BForward As Boolean = True) As Range
' Get nearest paragraph range with given style SStyle
' Returns whole matched paragraph range or Nothing for no match
' Setting BForward to False searches backward

' Check whether SStyle is valid in the document
If Not IsStyle(SStyle) Then
' Invalid style so just exit
Set FindParagraphStyleRange = Nothing
Exit Function
End If

' Declare working range and set to starting Selection
Dim r As Range
Set r = Selection.Range
r.Collapse ' Avoid any issues but should be superfluous

' Check whether located at beginning first paragraph
If r.Start = r.Paragraphs.First.Range.Start And BForward Then
' Move away from Start of paragraph (avoids immediate detection issue)
r.Move Unit:=wdCharacter
End If

' Setup search parameters
With r.Find
.ClearFormatting
.MatchWholeWord = False
.MatchSoundsLike = False
.MatchCase = False
.MatchWildcards = False
.MatchSuffix = False
.Format = False
.MatchPhrase = False
.IgnoreSpace = False
.IgnorePunct = False
.Highlight = wdUndefined ' Ignore state

' Main options for this search
.Wrap = wdFindStop ' maybe wdFindContinue?
.Style = SStyle
.Forward = BForward
End With

r.Find.Execute Replace:=wdReplaceNone

' Check search result and return appropriate value
If r.Find.Found Then
' Found desired paragraph style, so return paragraph range
Set FindParagraphStyleRange = r
Else
' No matching paragraph style was found
Set FindParagraphStyleRange = Nothing
End If
End Function

That’s a lot of steps for such a simple function, in principle, which is why it was worth creating a separate function.

Affiliate Links

If you're interested in using Word or another tool related to the article, check out these affiliate links. I may make a small commission if you purchase when using them, but there is no increase in cost for you, and it helps to support this site and associated content.

I've been using Microsoft for Business for commercial use (that's us writers) on one of the lower pricing tiers for years. I get to use my macros, have online storage, and don't have to worry about software updates.