Microsoft Word VBA Macro - One Paragraph Find-Replace Styles - vba

I am executing a style search in Microsoft Word using a VBA Macro.
My goal is to perform certain actions once for every style found in the document.
The macro works correctly on documents that have at least two paragraphs, but the macro does not alert the style correctly in a document that has exactly one paragraph in it. It seems strange that when I enter a new paragraph mark, the styles are found, even though I did not add any new text or styles to the document, just an extra blank paragraph mark. Does anyone know what is wrong with my macro and how I can fix this? Thanks for taking a look.
Sub AlertAllStylesInDoc()
Dim Ind As Integer
Dim numberOfDocumentStyles As Integer
Dim styl As String
Dim StyleFound As Boolean
numberOfDocumentStyles = ActiveDocument.styles.count
For Ind = 1 To numberOfDocumentStyles
styl = ActiveDocument.styles(Ind).NameLocal
With ActiveDocument.Content.Find
.ClearFormatting
.text = ""
.Forward = True
.Format = True
.Style = styl
Do
StyleFound = .Execute
If StyleFound = True Then
' actual code does more than alert, but keeping it simple here'
MsgBox styl
GoTo NextStyle
Else
Exit Do
End If
Loop
End With
NextStyle:
Next
End Sub

I don't understand why ActiveDocument.Content is not working, but replacing it with ActiveDocument.Range(0,0) appears to resolve the issue (tested in Word 2016).
With ActiveDocument.Range(0, 0).Find

Related

Find/Replace an Inserted Check Box Symbol with a Check Box Content Control

I would like to find/replace all inserted check box symbols with checkbox content controls. The symbol's font is Wingdings (either 111 or 168). Below is the code I started with, but I hit a wall when I realized that Word find doesn't recognize the symbol. I appreciate any help or guidance. Thank you.
Sub ReplaceUnicode168()
Dim objContentControl As ContentControl
With ActiveDocument
Set objContentControl = ActiveDocument.ContentControls.Add(wdContentControlCheckBox)
objContentControl.Cut
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Wrap = wdFindContinue
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.Text = Chr(168)
.Replacement.Text = "^c"
.Execute Replace:=wdReplaceAll
End With
End With
End Sub
I suggest that you try to find/replace these two particular characters using
.Text = ChrW(61551)
for the "111" WingDings Character and
.Text = ChrW(61608)
for the "168" WingDings character.
Be aware that the way Word encodes these characters is not very helpful. As far as Find/Replace is concerned, you have to use these Unicode Private Use Area encodings.
If you actually select the character and use VBA to discover its code using e.g.
Debug.Print AscW(Selection)
the answer is always 40 (and the Font of the character will probably be the same as the Surrounding font) Pretty useless. In older versions of Word you used to be able to look for the 40 character and find these characters, but I don't think that's possible now. But if you select the character and use
Sub SymInfo()
With Dialogs(wdDialogInsertSymbol)
' You won't see .Font and .CharNum listed under the
' properties of a Word.Dialog - some older Dialogs add
' per-Dialog properties at runtime.
Debug.Print .Font
Debug.Print .CharNum
End With
End Sub
Then you get the font name (Wingdings in this case) and the private use area character number, except it's expressed as a negative number (-3928 for Wingdings 168). The character to use in the Find/Replace is 65536-3928 = 61608.
Alternatively, you can find the private use area code by selecting the character, getting its WordOpenXML code, then finding the XML element that gives the code (and the font). Ideally use MSXML to look for the element but the following gives the general idea.
Sub getSymElement
Dim finish As Long
Dim start As Long
Dim x As String
x = Selection.WordOpenXML
start = Instr(1,x,"<w:sym")
' Should check for start = 0 (not found) here.
finish = Instr(start,x,">")
Debug.Print Mid(x,start, finish + 1 - start)
and for the 168 character you should see something like
<w:sym w:font="Wingdings" w:char="F0A8"/>
(Hex F0A8 is 61608)
There may be a problem where Word could potentially map more than one font/code to the same unicode private use area codepoint. There is some further code by Dave Rado here but I do not think you will need it for this particular problem.
After some follow-up, the following seems to work reasonably well here:
Sub replaceWingdingsWithCCs()
Dim cc As Word.ContentControl
Dim charcode As Variant
Dim ccchecked As Variant
Dim i As Integer
Dim r As Word.Range
' Make sure the selection point is not in the way
' (If the selection contains one of the characters you are trying to
' replace, Word will raise an error about the selection being in a
' plain text content control.
' If the first item in the document is not a CC,
' it's enouugh to do this:
ActiveDocument.Select
Selection.Collapse WdCollapseDirection.wdCollapseStart
' Put the character codes you need to look for here. Maybe you have some checked boxes too?
charcode = Array(61551, 61608)
' FOr each code, say whether you want a checked box (True) or an unchecked one.
ccchecked = Array(False, False)
For i = LBound(charcode) To UBound(charcode)
Set r = ActiveDocument.Range
With r.Find
.ClearFormatting
With .Replacement
.ClearFormatting
.Text = ""
End With
.Forward = True
.Wrap = wdFindStop
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.Text = ChrW(charcode(i))
Do While .Execute(Replace:=True)
Set cc = r.ContentControls.Add(WdContentControlType.wdContentControlCheckBox)
cc.Checked = ccchecked(i)
r.End = r.Document.Range.End
r.Start = cc.Range.End + 1
Set cc = Nothing
Loop
End With
Next
Set r = Nothing
End Sub

VBA - WORD Deleting rows after and before specific word

I'm trying to clean up my Word document using VBA.
What i need to do is to find a specific word (usually a website) then select the line it is in and then select and then remove text line above(only 1 line), the lines under that website line as well (sometimes more than 2 - if the text is longer). I'll try to show you how the line looks now.
Something happend at someplace!
website.com 08.01.2019
Something happend at someplace and it was a bad person doing it.
He used spaces instead of tabs in his code.
TAG-important stuff
The website 99% of times doesn't show in the 1st line, so im trying to find the 2nd line.
There are other websites and texts i would like to keep (so it would skip newsbetter.com)
In every document there are about 30-100 pharagraphs like the one I've typed earlier (the ones do delete)
I've been searching on the internet for a possible solution but they usually are for Excel. I think that strings are not working for me here.
Sub ScratchMacroII()
Dim oRng As Word.Range
Set oRng = ActiveDocument.Range
With oRng.Find
.Text = "news.pl"
While .Execute
While oRng.Find.Found
oRng.Select
Selection.Expand Unit:=wdParagraph
Selection.Delete
Wend
End With
End Sub
I expected the result to delete the whole pharagraph, but it justs deletes one line and leaves the other ones. I need some pointers since I'm new at VBA.
The following code, based on the sample in the question, searches the term from the beginning to the end of the document. When found, the paragraphs following and preceding the term are deleted. The search Range is then set to the document content following the found instance so that the same instance is not picked up repeatedly.
Note that I included Find.Wrap = wdFindStop to prevent the code from cycling through the document again. It's also necessary to repeat the Execute method within the loop, rather than trying to loop on it. While...Wend is an old type of loop; preferred is Do While...Loop.
Sub ScratchMacroII()
Dim oRng As Word.Range
Dim para As Word.Paragraph
Dim found As Boolean
Set oRng = ActiveDocument.Range
With oRng.Find
.Text = "news.pl"
.wrap = wdFindStop
found = .Execute
Do While found
Set para = oRng.Next(wdParagraph, 1).Paragraphs(1)
para.Range.Delete
Set para = oRng.Next(wdParagraph, -1).Paragraphs(1)
para.Range.Delete
oRng.Collapse wdCollapseEnd
oRng.End = ActiveDocument.content.End
found = oRng.Find.Execute
Loop
End With
End Sub

What's the best way to update LINK fields with path of current document using VBA?

So I have 4 documents, 3 excel spreadsheets and 1 document. All four are in the same directory "test." All four will always remain in the same directory no matter what. However, the goal of the document is to build a report out of the three spreadsheets for multiple properties. This means that the paths would be different for every different computer that it was used on. I want a macro that will auto-update the LINK fields with the current path but I'm running into some trouble.
So far I have
SendKeys "%{F9}"
Dim path As String
path = ActiveDocument.path
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "C:\\Users\\Gianni\\Desktop"
.Replacement.Text = path
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
SendKeys "%{F9}"
There are two problems with this from what I can tell. If I just view the fields manually and run the code without the first SendKeys command, the find & replace works. With the first SendKeys command, however, the code doesn't replace the text with the new path. Still, the path that pastes ends up breaking the link anyway. How do I go about fixing these?
Often, it's better in Word to work with the underlying object model of a Word document, than trying to reproduce exactly what you do as a user. Understanding how Word works, from a user point-of-view is very important and there are many things you you're able to do by converting those steps into a macro. But digging into the object model is generally faster and more accurate.
Changing a LINK field code is one of those things - and like many things, there's more than one way to go about it. Here are two possibilities.
The first is close to how you're approaching it, by manipulating the field code. Note that it's not necessary, using VBA, to actually display the field code. The object model lets you manipulate it "behind the scenes".
This procedure loops all the Fields in the document, checks whether each is a LINK field. If it is, the alternate path is substituted in the field code for the original path using the VBA Replace function, then this is written to the field code.
'Assumes the linked Excel workbook is an inline shape
Sub ChangePathInLinkField()
Dim doc As word.Document
Dim fld As word.Field
Dim strSearchPath As String
Dim strReplacePath As String
Dim strNewFieldCode As String
Set doc = ActiveDocument
strSearchPath = "C:\\Users\\[user name]\\Documents\\SampleChart.xlsx"
strReplacePath = "C:\\Test\\SampleChart.xlsx"
For Each fld In doc.Fields
If fld.Type = wdFieldLink Then
strNewFieldCode = Replace(fld.code.Text, strSearchPath, strReplacePath)
fld.code.Text = strNewFieldCode
End If
Next
doc.Fields.Update
End Sub
The second procedure shows how the link path can be changed for Shapes as well as InlineShapes (if you have a Shape you can't see the LINK field). It can also be used only on InlineShapes, of course. This loops the collection, checks whether the object is a linked OLE object and, if it is, changes the path.
Which one to use will depend on your situation - test them both and decide based on that.
'Alternate: works with OLE object
Sub ChangePathInLinkedObject()
Dim doc As word.Document
Dim ils As word.InlineShape
Dim shp As word.Shape
Dim strReplacePath As String
Dim i As Long
Set doc = ActiveDocument
strReplacePath = "C:\Users\Cindy Meister\Documents\SampleChart.xlsx"
strReplacePath = "C:\Test\SampleChart.xlsx"
'For Each doesn't work because updating the field
'destroys the object, so it loops over the same object
'For this reason it's also necessary to work backwards through the document
For i = doc.InlineShapes.Count To 1 Step -1
Set ils = doc.InlineShapes(i)
If ils.Type = wdInlineShapeLinkedOLEObject Then
ils.LinkFormat.SourceFullName = strReplacePath
ils.LinkFormat.Update
End If
Next
For i = doc.shapes.Count To 1 Step -1
Set shp = doc.shapes(i)
If shp.Type = msoLinkedOLEObject Then
shp.LinkFormat.SourceFullName = strReplacePath
shp.LinkFormat.Update
End If
Next
End Sub
Instead of using SendKeys you can show field codes with:
ActiveDocument.ActiveWindow.View.ShowFieldCodes = True
and to show field values
ActiveDocument.ActiveWindow.View.ShowFieldCodes = False
That may help with your first problem.
To see how to implement relative paths in Word, check out the solution I've posted at:
http://windowssecrets.com/forums/showthread.php/154379-Word-Fields-and-Relative-Paths-to-External-Files
Since you're working with LINK fields, you'll need the macro solution there.

Microsoft Word macro to alter heading styles

I am attempting to create a macro in Word that alters the style of a set of ~150 unique headings. All styles must be identical. My current code works and changes the formatting correctly, but only one heading at a time.
Simply put, it's ugly.
I'm looking for something I can reuse, and possibly apply to more projects in the future.
Maybe using the loop command? I don't know, I'm still somewhat new using VBA.
Sub QOS_Headings()
Dim objDoc As Document
Dim head1 As Style, head2 As Style, head3 As Style, head4 As Style
Set objDoc = ActiveDocument
Set head1 = ActiveDocument.Styles("Heading 1")
Set head2 = ActiveDocument.Styles("Heading 2")
With objDoc.Content.Find
.ClearFormatting
.Text = "Section A.^p"
With .Replacement
.ClearFormatting
.Style = head1
End With
.Execute Wrap:=wdFindContinue, Format:=True, Replace:=wdReplaceOne
End With
End With
End Sub
If there is no way in which you can identify the heads you want automatically you may have to write everything once. Create a separate function for this purpose. It might look like this:-
Private Function SearchCriteria() As String()
Dim Fun(6) As String ' Fun = Designated Function return value
' The number of elements in the Dim statement must be equal to
' the number of elements actually declared:
' observe that the actual number of elements is one greater
' than the index because the latter starts at 0
Fun(0) = "Text 1"
Fun(1) = "Text 2"
Fun(2) = "Text 3"
Fun(3) = "Text 4"
Fun(4) = "Text 5"
Fun(5) = "Text 6"
Fun(6) = "Text 7"
SearchCriteria = Fun
End Function
You can add as many elements as you wish. In theory it is enough if they are unique within the document. I shall add some practical concerns below. Use the code below to test the above function.
Private Sub TestSearchCriteria()
Dim Crits() As String
Dim i As Long
Crits = SearchCriteria
For i = 0 To UBound(Crits)
' prints to the Immediate Window:
' select from View tab or press Ctl+G
Debug.Print Crits(i)
Next i
End Sub
Now you are ready to try to actually work on your document. Here is the code. It will not effect any changes. It's just the infrastructure for testing and getting ready.
Sub ChangeTextFormat()
Dim Crits() As String
Dim Rng As Range
Dim Fnd As Boolean
Dim i As Long
Crits = SearchCriteria
For i = 0 To UBound(Crits)
' find the Text in the document
Set Rng = ActiveDocument.Content
With Rng.Find
.ClearFormatting
.Execute FindText:=Crits(i), Forward:=True, _
Format:=False, Wrap:=wdFindStop
Fnd = .Found
End With
If Fnd = True Then
With Rng
Debug.Print .Text
' .MoveStart wdWord, -2
' With .Font
' .Italic = True
' .Bold = True
' End With
End With
Else
Debug.Print "Didn't find " & Crits(i)
End If
Next i
End Sub
The first half of the procedure will find each of the search criteria in your document using the same kind of loop as you already know from the test procedure. But now the text is fed to the Find method which assigns the found text to the Rng range. If the item is found you now have a handle on it by the name of Rng.
The second half of the sub deals with the outcome of the search. If the text was found the found text (that is Rng.Text) is printed to the Immediate window, otherwise the original text Crits(i) with "didn't find".
If the text was found you want to assign a style to it. But before you can do so you should deal with the difference between the text you found and the text you want to format. This difference could be physical, like you didn't write the entire length of the text in the criteria, or technical, like excluding paragraph marks. In my above sub there is just random code (extending the Rng by two preceding words and formatting everything as bold italics). Consider this code a placeholder.
For your purposes code like this might do the job, perhaps. .Paragraphs(1).Style = Head1 Actually, that is rather a different question, and I urge you not to rush for this result too fast. The part you now have needs thorough testing first.

Why won't my macro run automatically in Word?

This is my first time attempting VBA in Word although I've used it quite a bit in Excel. What I am trying to do is automatically execute my code after opening my Word template document. Here is the code I've placed in my Word template Module:
Private Sub AutoOpen()
Dim myValue
myValue = InputBox(prompt:="What is the client name", Title:="InputBox", Default:="Type your client name here")
stringReplaced = stringReplaced + "<Replace>"
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<Replace>"
.Replacement.Text = myValue
.Wrap = wdFindContinue
.ClearFormatting
.Replacement.ClearFormatting
.Replacement.Highlight = False
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
End Sub
I am using Word 2010. My code runs like I want it to when I manually go in and click run. However, when I close out of my Word document and reopen nothing happens at all. I've Googled the problem trying to find out a solution (and have attempt different versions of AutoOpen), but I can't figure out what I am doing wrong. Any ideas on why the AutoOpen doesn't execute automatically?
Thanks!
You should place your code in standard Module in your Template document. Next, change sub name from AutoOpen() into:
Sub AutoExec()
'..... your code here .....
End Sub