We are building several functions to streamline our previous move sentence macro. Toward this end, we implement a function to delete any spaces at the end of a given paragraph, but an additional version is included for a given range.
Thanks for your interest
This content is part of a paid plan.
The Problem
In the original article, the base macro to move a sentence forward or backward in a document is awkwardly long. In this function, we start the process of simplifying the longer macro by separating out the steps to delete any undesired spaces at the end of a given paragraph.
Delete Paragraph End Spaces
To create a function, we usually identify a group of steps in a macro that performs a specific, usually repeated, task. When writing the move sentences macros, we had to check and delete any spaces at the end of a paragraph. This task occurred four times (two possible paragraphs per direction), so that seems like a good place to start a candidate function.
The steps to delete unwanted spaces at the end of a paragraph was as follows:
This particular set of steps worked with a range variable marking the original sentence to move, but while we’re generalizing the steps to a function, we’ll convert it to work with a paragraph since that was the most common case (and it's easier).
Create function skeleton
We start by creating the function skeleton.
We indicate the returned data type with the “As Long” statement after the Function name and parentheses. Counting numbers in Word are usually Integer or Long, but a Long number type is simply a bigger form of an Integer with a much higher maximum number.
In this function, we’re returning how many spaces were deleted which is a counting number, so we add a step at the end to return that value which we accomplish by just assigning a value to the function name.
We just set a value of zero for now because we don’t know what it is yet.
What parameters to use?
Often, we want to reuse a function with other macros, so we create it to receive generic document elements (or whatever data is necessary). With this one, we’ll work on a given paragraph. Although we could design it using Ranges instead (see extended content below).
We just have to make a choice. Applying it to ranges is probably the more general purpose case, but sometimes there is an easier approach. I chose to use a paragraph since I know the exact structure of the paragraph. Specifically, a paragraph ends with a paragraph mark and not some random character like would happen with a generic range.
So, we add a Paragraph parameter, which we'll call TargetParagraph, to the function inside the parentheses.
We add "As Paragraph" after the variable name to tell the function that the data type is a Paragraph. A Paragraph is an object type in Word VBA which has its own methods (actions) and properties (data). It's almost like a document range but more specific.
Fill the subroutine
Now fill the empty function. In this function, we’re now assuming the user gives us a valid, whole paragraph which simplifies the logic.
Sometimes I create subroutines from scratch since it can be easier to think through the steps like that, but other times I copy the steps over from an existing macro to “save some time.” Neither approach is wrong, but you’ll probably modify any copied steps since you’re usually implementing the function with slightly different assumptions as opposed to creating it inside the original macro.
Define the working Range
We first set a working range to span the given TargetParagraph.
Why a range?
Huh? Wait.
Why a range when I thought you just said something about a paragraph—
Just hold on. You might wonder why I wanted a Paragraph parameter for the function if we’re just going to define a working range based on it anyhow.
If we ask the user for a Paragraph, we know it includes the whole paragraph including the paragraph mark. We don’t have to check anything extra and worry about any special cases. A range version is good if you want to do that, but I liked the simplified logic. It gives us a more targeted macro with fewer options to consider.
While we can perform some actions using the Paragraph directly, we define a working range since Ranges allow us to navigate and modify document content more generally.
Define the range variable
We want that entire paragraph range to begin the function.
Recall we have to Set Ranges since they are more than just pure values like a number or plain text.
Omit the paragraph mark
Remove the paragraph mark from the End of the Range.
This is an example of knowing a specific starting state and how we can take a specific action based on it. If we had a general target range parameter, we would need to add some tests to verify our starting state.
Collapse the range
We collapse the working range since we’re planning on selecting the spaces using the range.
We collapse toward the end using the Direction option since we want to eliminate any spaces there. Now our empty range is positioned just before the paragraph mark and presumably just after any spaces that may exist.
Select any spaces
Select any spaces before the range by moving the Start of the Range backward over them.
Remember MoveStartWhile doesn’t change the End position of the Range, so if any spaces exist, we’ll be spanning text after this step.
Get number of characters moved
However, I need to track how many characters the Start position moved, so I can check whether any spaces were spanned. Thus, we modify the command to store the result in a variable.
Don’t forget we need to include the arguments in parentheses if we use the method like a function and store the returned value.
Delete any spaces
We want to delete any spaces contained in MyRange, but we can’t just use
Why not?
What if there were no spaces at the end of the paragraph?
Then this Delete command would delete the first character to the right of the empty MyRange, so we need to first check whether the MyRange Start position changed. We previously stored the number of characters in the CharactersMoved variable.
The CharactersMoved variable will have a negative value if the Start position of MyRange moved backward in the document.
Delete a straggling space
Word sometimes reinserts a space as a helpful feature during real-time editing. I like it there, but for macros, it’s … well, not as a helpful.
We need to check whether Word actually inserted the space and delete it if so. If it is there, it will be reinserted to the right of the now empty MyRange.
Remember the “next” character after an empty range (when the Start and End positions are the same) is actually the “First” character. This is not true if the Range spans any text (see extended content below for an example).
Change return value
We now know how many spaces were spanned, so we can set the return value at the end of the function appropriately.
This value is passed back to the macro that called the function.
The Abs() is the absolute value function. Recall an absolute value is the positive value of whatever number is given to it. It is used here since the MoveStartWhile method will return negative values for moves backward in the document, but the number of spaces deleted is inherently a positive number. It’s a preference.
Final function
Now let’s put the function steps together. This one returns how many spaces were deleted.
Notice it looks a lot like a regular macro (because it is) except we require a Paragraph argument when it’s used, the function requires a return type, and we return a value at the end. Subroutines even skip that last two parts.
The steps are similar to what we did in the original macro, but we made some changes based on the assumptions when applying it to a known paragraph.
In practice, I prefer functions since we can send some information back about what just happened during the function, but the calling macro can just ignore the value by not using or storing it.
Range Version
What would change if we implemented the subroutine using a Range parameter instead?
Delete paragraph end spaces function using a Range parameter
This is an interesting example of how small choices affect our solution and the work involved in implementing them. Sometimes choosing the simpler option is better.
The result (changes made off screen) is as follows.
Of course, the basic features and many of the steps are the same as in the Paragraph parameter version. It gets rid any spaces at the end of the given Range.
This version is more general since it accepts any Range, not just paragraphs, so it is preferable in that regard, but …
Let’s talk about the extra work
Generality usually comes at a price. Fortunately, the price to pay is relatively small here, and it also gave us a springboard to talk about a few more things.
What about spaces outside end of range?
We need to at least briefly consider other conditions that may affect how we write the macro. For example, do we worry about any ending spaces outside the end of the given TargetRange?
Generally speaking, I would say no (don't change anything outside the given TargetRange) mostly because that’s not what the name of the function implies "DeleteRangeEndSpaces" (I do have exceptions though), so we won’t do anything with this.
We didn’t have to worry about this with the Paragraph parameter version.
Check for paragraph mark
Since we have a general TargetRange, we now need to check whether the paragraph mark is present before removing it from the range. This is a preference that seems obvious to exclude (to me at least) since paragraphs will be a common use case, but we don't want to force the user to always manually remove the ending paragraph mark.
If you're unsure, you could also set up an extra Optional parameter to give the user a choice, but we'll cover those later.
New gotcha with ending space
This version introduces a small gotcha if the next character after the TargetRange is already a space. The last steps of the subroutine would delete a trailing space because the check for the last space can’t tell the difference between one that was already present in the document and one that Word reinserted during the macro.
Avoid side effects
It’s not the end of the world if we ignore this gotcha since we’re just deleting an extra unintended space to the right of the TargetRange, but such “side effects” should generally be avoided because we’ll forget about them as time passes. The user just doesn’t expect a macro to act like that … so it shouldn’t.
Next character issue
We have another small compounding problem.
In the process of fixing the gotcha, we run up against another small issue where the next character in the intuitive sense is different for empty Ranges compared to Ranges that span any text. This pops up several times in other macros, so we’ll eventually turn this into a separate small function.
Since we now allow the user to provide any TargetRange to the subroutine, we have to allow for ranges whether they are empty or not. On top of that, storing the next character must happen after the paragraph mark check, or we could get a third spurious result in isolated cases.
See what’s happening …
Additional next character check
We finally add the extra next character check to the condition in the last macro steps.
Just a little messier
Yikes. That’s more than such a simple change from a Paragraph to a Range parameter might suggest is necessary.
None of the above was particularly difficult individually, but on the whole it was just messier to implement which is why I initially created the simpler version using a Paragraph parameter.