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:
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:
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).
As a conditional statement, we roughly get:
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.
Then we just exit the function as we’ve done before.
Putting it together, our style validation check is:
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.
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.
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.
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.
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,
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.
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.
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.
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.
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:
We’ll use an And operator since both conditions must be True before we adjust the range position.
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.
We do not reference the Paragraph Range this time since a Paragraph has an associated paragraph Style property. The condition is then:
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:
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Final Function
Putting it all together, our longer-than-expected paragraph style search function is:
That’s a lot of steps for such a simple function, in principle, which is why it was worth creating a separate function.