Add characters before and after selection using VBA - vba

I tried to write a macro to would add a certain character before a selected text in Word. This works fine. I'd like to add one more feature. When no selection is made the macro should select a word where the cursor is and then add a certain character.
I have the following code:
Sub AddChar()
If Len(Trim(selection.Text)) >= 1 Then
Trim (selection.Text)
selection.InsertBefore Chr(187)
selection.InsertAfter Chr(171)
Else
selection.Words(1).Select
selection.InsertBefore Chr(187)
selection.InsertAfter Chr(171)
End If
End Sub

I am assuming that your question is about the case where you have a "point" selection.
When you have a "point" selection, although it looks as if nothing is selected, the Selection object actually contains the character after the insertion point (you can check by typing
?Selection
in the Visual Basic Editor's Immediate window)
So the problem is that the selection will still have a length of 1, so your initial test
If Len(Trim(selection.Text)) >= 1 Then
will not work.
What you need to do is check the Selection.Type. You will find that the Object Browser lists several types, but for example the following code will solve the immediate problem:
If Selection.Type = wdSelectionType.wdSelectionIP Then
' code for a "point" selection
Else
' code for the case where something is already selected
End If

Related

Highlighting text in a Word file - different behaviour when text is in a table

I have designed and built an algorithm in my vb.net VSTO to break up any text into it's sentences. It's used by a language specialist to determine the readability of text (I'm also automating the readability analysis). I do not rely on the Find with Wildcards as that is too limited. The algorithm has to deal with quoted text (even when part of a larger sentence), lists and headings.
So far so good. The algorithm works as intended.
I thought it be useful to show in colors what the outcome of the 'break up text into sentences' algorithm is by highlighting the various texts in the document. Doing this I encountered an inconsistency in Word's behaviour (using Office 2016 Professional 32-bit on Win10). I'd like to share this and see if anyone can provide more insight before I devise a solution. Am I missing anything?
Outside a table, I can set a range to any text and then set the .HighlightColorIndex property and the color is changed and visible in the Word editor.
Inside a table cell, it works the same AS LONG AS there is no paragraph marker (vbCr) immediately following the text (I'm not including the vbCr in my range). In such case when the .HighlightColorIndex is changed (confirmed in the debugger) the color is not visibly changed in the Word editor.
It only works when I include the paragraph marker in my range. Outside a table that is not required.
Basic code flow (partly non-code) with some additional comments for clarity
For each para as Paragraph in selectedRange.paragraphs
' Identify a sentence by looping the para.range.text
' looking at punctuation marks, quotes, abbreviations, false positives etc.
... complicated logic ...
' If sentence identied, find it so we have the right range.
' This find is actually in a Sub FindSentence (rng, sentence)
' shown here for readability.
' The sub includes some complicated logic to overcome the Find text length limit.
para.range.find (sentence.text)
if para.range.found then
' This code is actually in Sub Highlight(rng, sentence)
' shown here in the main code for readability.
' The debugger shows that the properties are changed.
' If range is in a Table then highlight is not shown in Word
' unless the found para.range includes the vbCr.
' The exact same logic works fine when text is not in a table.
' The table behaviour is problematic because a table cell
' can contain multiple sentences and only the last one would have the vbCr.
Select Case sentence.type
Case EtSentenceType.Normal
rng.HighlightColorIndex = WdColorIndex.wdGray25
rng.Font.ColorIndex = WdColorIndex.wdAuto
Case EtSentenceType.Question
rng.HighlightColorIndex = WdColorIndex.wdBrightGreen
rng.Font.ColorIndex = WdColorIndex.wdAuto
Case EtSentenceType.Exclamation
rng.HighlightColorIndex = WdColorIndex.wdRed
rng.Font.ColorIndex = WdColorIndex.wdWhite
Case EtSentenceType.SingleQuote, EtSentenceType.DoubleQuote
rng.HighlightColorIndex = WdColorIndex.wdTurquoise
rng.Font.ColorIndex = WdColorIndex.wdAuto
Case EtSentenceType.Heading
rng.HighlightColorIndex = WdColorIndex.wdYellow
rng.Font.ColorIndex = WdColorIndex.wdAuto
Case EtSentenceType.List
rng.HighlightColorIndex = WdColorIndex.wdPink
rng.Font.ColorIndex = WdColorIndex.wdAuto
End Select
end if
End loop
Next

Insert Building Blocks using Word VBA

I'm trying to insert a formatted table that I have saved in word named "DV Table" as part of the building blocks using VBA. I need this table to be inserted at the 13th paragraph of the word document.
Here's my code below. The first 3 lines just sets the selection to be at the 12th paragraph and create a new paragraph (13) after that. The last line of code is to insert the table. But when I run this, it gives the error.
Compile Error: Sub or Function not defined
I guess that this is not the proper way of defining the location. Would like some help on this. Thanks.
ActiveDocument.Paragraphs(12).Range.Select
Selection.EndKey Unit:=wdLine
Selection.Paragraphs.Add
ActiveDocument.AttachedTemplate.BuildingBlockEntries("DV Table" _
).Insert Where:=Paragraphs(13).Range.Select, RichText:=True
The Where parameter requires a Range object. There are two problems with Paragraphs(13).Range.Select
it's a method - it's an action, selecting something, not returning an object
Paragraphs(13) isn't "fully qualified" - VBA doesn't know what it is/what is meant.
One possibility would be
ActiveDocument.Paragraphs(13).Range
Notice ActiveDocument. preceding Paragraphs: this "fully qualifies" Paragraphs(13) - it tells VBA to what that belongs. And, since Where requires a Range object, Paragraphs(13).Range should be a correct "target" (I have not tested your code).
Generally, it's preferable not to work with Selection, just with Range objects. There's usually no need to actually select something using VBA. An alternative to the code snippet in the question could be
Dim rng As Word.Range
Set rng = ActiveDocument.Paragraphs(13).Range
rng.Collapse wdCollapseEnd 'like pressing right-arrow for a selection
rng.InsertParagraphAfter
rng.Collapse wdCollapseStart ' like pressing left-arrow for a selection
'rng.Select ' for testing / demo purposes
ActiveDocument.AttachedTemplate.BuildingBlockEntries("DV Table" _
).Insert Where:=rng, RichText:=True
In this case, the selection in the document does not change. There's no screen flicker; and code executes more quickly. This way of working takes getting used to, but once one is familiar with it, it's much easier to recognize what the code should be doing... Selection is rather vague as to what is being manipulated, especially if there's a lot of code using it.

VBA: Stop Word from adding new line to selected text

Whenever I select a section of text in MS Word using this code:
Dim aRange As Range
Set aRange = ActiveDocument.Range( _
Start:=ActiveDocument.Paragraphs(1).Range.Start, _
End:=ActiveDocument.Paragraphs(3).Range.End)
aRange.Select
everything is fine except that Word automatically alters the selection to add a new line character at the end of the selection. How to avoid it? Perhaps it is possible to change the selection so that it will not include a new line character.
My question is similar to another one already asked, but I would like it to do it using VBA.
When you work with selections on the keyboard you use Shift + Left/Right Arrow keys to change the extent of the selection. In the Word object model there's an equivalent you can use with the Range object; actually, there's a set of methods: MoveStart, MoveEnd, MoveWhile, MoveUntil
In this case, you need the MoveEnd method. You can move the end point by a specific set of units, such as characters, words, paragraphs - you want to move by one character, going "backwards", so:
aRange.MoveEnd wdCharacter, -1
aRange.Select
This should work:
doc.Paragraphs(1).Range.Select
Selection.MoveEnd wdCharacter, -1
Selection.Copy

Getting the previous Word in VBA using selection.previous wdword, 1 bug

I'm trying to write a macro to type the previous word at the cursor.
the problem is when i'm using "selection.previous wdword, 1" to get the previous character, it sometimes get the 2 previous characters and it seems like a bug. when i press "delete" button it works and it is very strange to me.
I'd glad if you help.
my ultimate goal is to create a calendar converter inside word using this code.
here is how i test it:
MsgBox Selection.previous(unit:=wdWord, Count:=1)
it is the same using next :
MsgBox Selection.Next(unit:=wdWord, Count:=1)
instead of next word, sometimes it returns the word after!
For example this is the text: during the flight on 21/3/1389
If the cursor is right after the 1389, msgbox selection.previous(1,1) would show "/"; if the cursor is after a space after 1389 it shows "1389". The problem is, I think, the space. My question is if there is any alternative to read the previous word instead of this command (Selection.previous(unit:=wdWord, Count:=1))
Word is not buggy - it's behaving as designed. Something has to tell Word where words start and end. When the cursor stands to the right of a space it's (quite logically) at the beginning of the next word. So going one word back is going to pick up 1389 instead of /.
You can work around this in your code. I'm sure there's more than one way to do it, but the following works for me in a quick test:
Sub GetPrevWord()
Dim rngSel As word.Range, rngPrev As word.Range
Set rngSel = Selection.Range
Set rngPrev = rngSel.Duplicate
rngPrev.MoveStart wdCharacter, -1
If Left(rngPrev.Text, 1) = " " Then
rngPrev.Collapse wdCollapseStart
End If
rngPrev.Select
MsgBox Selection.Previous(unit:=wdWord, Count:=1)
rngSel.Select
End Sub
What it's doing is using two Ranges: one to hold the original selection, the other to work with (rngPrev). rngPrev is extended backwards by one character and this character is evaluated. If it's a space then rngPrev is collapsed to its starting point. (Think of it like pressing the left arrow key of a selection.) In any case, rngPrev is selected and your MsgBox code is run. Finally, the original range is selected again.

Automating Mail Merge

I need to dynamically generate word documents using text from an Access database table. The caveat here is that some of the text coming from the database will need to be modified into MergeFields. I am currently using Interop.Word (VBA Macro) and VB.NET to generate the document.
My steps so far look like this:
Pull standard .docx Template
Fill In template with pre-defined filler text from table
Add MergeFields by replacing pieces of the filler text with actual mergefields
Attach Datasource and execute Mail Merge
After testing, I noticed that I cannot simply store MergeFields into the access database as a string, they do not feed over into the actual document. What I am thinking then is creating a table of known strings that should be replaced with MergeFields by the coding.
For Example:
Step 2 will insert "After #INACTIVE_DATE# your account will no longer be active." which will be in the database text.
Step 3 will find/replace #INACTIVE_DATE# with a MergeField «INACTIVE_DATE». I am using Word Interop to do this, so theoretically I can loop through the document.
I wasnt able to do a "Find And Replace" from text to MergeField, so how should I go about implementing this?
Tagging VBA additionally as I am seeking a "VBA" style answer (Word Interop).
You've left out quite a lot of detail, so I'll go about answering this in somewhat general terms.
What you want to do is definitely achievable. Two possible solutions immediately come to mind:
Replacing ranges using Find
Inserting tokens using TypeText
Replacing ranges using Find
Assuming the text has already been inserted, you can search the document for the given pattern and replace it with a merge field. For instance:
Sub FindInsertMerge()
Dim rng As Range
Set rng = ActiveDocument.Range
With rng.Find
.Text = "(\#*\#)"
.MatchWildcards = True
.Execute
If .Found Then
ActiveDocument.MailMerge.Fields.Add rng, Mid(rng.Text, 2, Len(rng.Text) - 2)
End If
End With
End Sub
Will find the first occurence of text starting with #, matches any string and ends with #. The contents of the found range will then be replaced by a merge field. The code above can easily be extended to loop for all fields.
Inserting tokens using TypeText
While I would normally advice against using Selection to insert data, this solution makes things simple. Say you have a target range, rng, you tokenize the database text to be inserted, select the range, start typing and whenever a designated mail merge field is found, insert a field instead of the text.
For instance:
Private Sub InsertMergeText(rng As Range, txt As String)
Dim i As Integer
Dim t As String
Dim tokens() As String
tokens = Split(txt, " ")
rng.Select
For i = 0 To UBound(tokens)
t = tokens(i)
If Left(t, 1) = "#" And Right(t, 1) = "#" Then
'Insert field if it's a mail merge label.
ActiveDocument.MailMerge.Fields.Add Selection.Range, Mid(t, 2, Len(t) - 2)
Else
'Simply insert text.
Selection.TypeText t
End If
'Insert the whitespace we replaced earlier.
If i < UBound(tokens) Then Selection.TypeText " "
Next
End Sub
Call example:
InsertMergeText Selection.Range, "After #INACTIVE_DATE# your account will no longer be active which will be in the database text."