Selection.style applied to wrong paragraph (and other issues) - vba

I have multiple documents coming from the same template. Each of them starts with a chapter number, so that I can build a table of contents that includes all of them. Since chapters have sections and subsections, I created a multilevel list style called MyList. Since they are different documents and all lists would start at 1, I need to specify the chapter number manually.
This can be achieved by writing {LISTNUM MyList \l 1 \s x} on the first line, x being chapter number. I wrote a macro to prompt for the chapter number, insert a carriage return and apply a new paragraph style to type the chapter name:
Chapter 1 (style: ChapNum)
Chapter name here (style: ChapName)
Normal text blah blah
The (working) code I have right now is:
Sub ChapterNumber()
Selection.Style = ActiveDocument.Styles("ChapNum")
ActiveWindow.View.ShowFieldCodes = True
Num = InputBox("Chapter number", "Random title")
SendKeys "^{F9}"
Selection.InsertBefore Text:="LISTNUM MyList \l 1 \s " + Num
SendKeys "%{F9}{RIGHT}{ENTER}"
End Sub
I have encountered a few issues until I finally got there, which are:
I was unable to apply a different paragraph style after the last Sendkeys using Selection.Style = ActiveDocument.Styles("ChapName") since the whole thing converted to ChapName style, not only the paragraph where the cursor was pointing.
I've read Sendkeys is not that reliable so, initially, instead of SendKeys "%{F9}{RIGHT}{ENTER}", I tried
ActiveWindow.View.ShowFieldCodes = False
Selection.EndKey Unit:=wdLine
Sendkeys "{ENTER}"
Neither of the first two commands worked, and I don't know why; I have used them in other macros and never had any problem. Would you please be so kind to clarify? Please take in mind my knowledge of vba is very limited.
Thank you.

Add the text "Chapter " to your template and apply the correct style. Add empty paragraphs in the other two required styles and your template is already setup for you to start typing. All that is then required is to add the LISTNUM field which can be done as shown below.
Sub ChapterNumber()
Dim Num As Long
Num = InputBox("Chapter number", "Random title")
Dim location As Range
With ActiveDocument
Set location = .Paragraphs(1).Range.Characters.Last
location.Move wdCharacter, -1
.Fields.Add Range:=location, Text:="LISTNUM MyList \l 1 \s " & Num
End With
End Sub
EDIT IN RESPONSE TO COMMENTS:
As you appear to have defined numbered styles in your template you do need to add the LISTNUM field. Instead you should set the start at number for the list template attached to the style.
Sub ChapterNumber()
Dim Num As Long
Num = InputBox("Chapter number", "Random title")
ActiveDocument.Styles("ChapNum").ListTemplate.ListLevels(2).StartAt = Num
End Sub

Related

MS WORD TOC : How to put a different color before or after the character ":" in the titles?

I have a text document in Word with several parts, I have created my table of contents. I created a TOC to be able to update it automatically, it is used for that.
By alt+F9 I have TOC \O "1-2" \H \U
You can see the sign \H that I need to have the links to the headers.
I can in the headers apply the desired color before and after the two points of my titles which are the form :
[xxxxx xxxx xxxx : (red)] [yyyyy yyyyy yyyyy (black)]
I want this color difference to show up in my summary (TOC) as well. So I add the instruction \* MERGEFORMAT
This gives : TOC \O "1-2" \* MERGEFORMAT \H \U
However, by doing this, I lose my links, as the instruction \H is no longer valid.
So I switched to VBA code.
But I don't know how to say :
xxxxxxx (in red) : xxxxxx (in black)
The x is variable, and the two points ( : ) is always present in my titles in the headers.
What would be the code to say that from the 2 points ( : ) the rest of the titles must be in black color. OR that before the 2 points ( : ) the color of the titles must be in red ?
For example:
Example/title: (in red) Here is my title (in black)
Example title two: (in red) Here is my second example (in black)
Other/example/additional: (in red) This is the last title (in black)
Thank you for your insights
EDIT :
Hi,
`Dim I As Integer, J As Integer
Dim MonTableau As Variant
Dim ListePositionsMots As String
Dim MonRange As Range
With ActiveDocument
If .TablesOfContents.Count = 0 Then
MsgBox "Aucune table des matières dans le document !", vbInformation
Exit Sub
End If
With .TablesOfContents(1)
J = 1
For I = 1 To .Range.Words.Count
If J <= 2 Then
If .Range.Words(I) <> "" Then ListePositionsMots = ListePositionsMots & I & ","
J = J + 1
End If
If .Range.Words(I) = Chr(13) Then J = 1
Next I
ListePositionsMots = Mid(ListePositionsMots, 1, Len(ListePositionsMots) - 1)
MonTableau = Split(ListePositionsMots, ",")
For I = LBound(MonTableau) To UBound(MonTableau)
Set MonRange = ActiveDocument.TablesOfContents(1).Range
MonRange.SetRange Start:=MonRange.Words(MonTableau(I)).Start, End:=MonRange.Words(MonTableau(I)).End
With MonRange
If .Text <> Chr(9) Then
.Font.ColorIndex = wdRed
.Case = wdUpperCase
End If
End With
Set MonRange = Nothing
Next I
End With
End With`
Good evening,
The above code works very well and allows me to colour the first 2 words of each of my titles in my table of contents.
x y (red) : x y z (black)
v w (red) : y z (black)
So it's possible.
As sometimes my titles exceed 2 words, I have to modify it.
I have to put the value of the word ; in this case the 2 points caracters ( : ) and not its position.
x y z (red) : (red or black) x y z (black)
But I don't know what vba code can do that, that's why I'm asking on this forum, I'm sure someone can help me?
Thanks.
EDIT 2 (26/05/2021 10:45) : I repeat, I just need the VBA code, nothing more, nothing less... Thanks.
You don't need any code for this. Without the \H switch, a Table of Contents will automatically reproduce any font colouring you apply to a Heading. At most, all you need to do is to refresh the Table of Contents.
Even without the \H switch, your Table of Contents will link to the referenced content via the page #s. All the \H switch does is enable the linking from the Table of Contents text.
In any event, it would be a waste of time trying to apply colouring to the Table of Contents with VBA (or manually), since anything that causes the Table of Contents to refresh (e.g. a print preview or printing the document) will erase all that colouring.
In any event, you don't even need a macro to colour the Table of Contents as you describe - all you need is a single wildcard Find/Replace operation on the Table of Contents, where:
Find = [!^t^13]#:
Replace = ^&
and you set the replacement colour to red. You could, of course, implement that as a macro, but I can't see why anyone would bother...
The code below will apply the color you require. To capitalise the TOC you should modify the font for your TOC styles to AllCaps
Sub ColorTOC()
Dim tocRange As Range
With ActiveDocument
If .TablesOfContents.Count = 0 Then
MsgBox "Aucune table des matières dans le document !", vbInformation
Else
With .TablesOfContents(1)
Set tocRange = .Range.Duplicate
tocRange.Collapse wdCollapseStart
Do Until tocRange.End = .Range.End
tocRange.MoveEndUntil ":"
tocRange.MoveEnd wdCharacter, 1
With tocRange
If .Text <> Chr(9) Then
.Font.ColorIndex = wdRed
End If
End With
tocRange.Collapse wdCollapseEnd
tocRange.MoveUntil vbCr
tocRange.Move wdCharacter, 1
Loop
End With
End If
End With
End Sub
When a TOC is updated the dialog below is displayed.
Choosing the first option will not cause the formatting to be lost, but the TOC will not include any newly added headings. The second option will include any newly added headings, but it will also remove the formatting.
If the document has the "Update fields before printing" option set (it should to ensure that page numbers etc. are correct) then the user will be prompted to update the TOC. This will occur both prior to printing and exporting as PDF. To ensure that your TOC has the correct formatting you will need to write code to respond to the DocumentBeforePrint event so that you can reapply the formatting.

Finding and Replacing with VBA for Word overwrites previous style

I'm writing a VBA script to generate word documents from an already defined template. In it, I need to be able to write headings along with a body for each heading. As a small example, I have a word document that contains only <PLACEHOLDER>. For each heading and body I need to write, I use the find-and-replace feature in VBA to find <PLACEHOLDER> and replace it with the heading name, a newline, and then <PLACEHOLDER> again. This is repeated until each heading name and body is written and then the final <PLACEHOLDER> is replaced with a newline.
The text replacing works fine, but the style I specify gets overwritten by the next call to the replacement. This results in everything I just replaced having the style of whatever my last call to my replacement function is.
VBA code (run main)
Option Explicit
Sub replace_stuff(search_string As String, replace_string As String, style As Integer)
With ActiveDocument.Range.Find
.Text = search_string
.Replacement.Text = replace_string
.Replacement.style = style
.Forward = True
.Wrap = wdFindContinue
.Format = True
.MatchWholeWord = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=wdReplaceAll
End With
End Sub
Sub main()
Dim section_names(2) As String
section_names(0) = "Introduction"
section_names(1) = "Background"
section_names(2) = "Conclusion"
Dim section_bodies(2) As String
section_bodies(0) = "This is the body text for the introduction! Fetched from some file."
section_bodies(1) = "And Background... I have no issue fetching data from the files."
section_bodies(2) = "And for the conclusion... But I want the styles to 'stick'!"
Dim i As Integer
For i = 0 To 2
' Writes each section name as wsStyleHeading2, and then the section body as wdStyleNormal
Call replace_stuff("<PLACEHOLDER>", section_names(i) & Chr(11) & "<PLACEHOLDER>", wdStyleHeading2)
Call replace_stuff("<PLACEHOLDER>", section_bodies(i) & Chr(11) & "<PLACEHOLDER>", wdStyleNormal)
Next i
Call replace_stuff("<PLACEHOLDER>", Chr(11), wdStyleNormal)
End Sub
Input document: A word document with only <PLACEHOLDER> in it.
<PLACEHOLDER>
Expected Output:
I expect that each heading will be displayed in the style I specified and can be viewed from the navigation pane like this:
Actual Output: However what I actually get is everything as wdStyleNormal style like this:
I think the problem can be solved by inserting a paragraph break between every style transition, but when I try using vbCrLF or Chr(10) & Chr(13) or vbNewLine instead of the chr(11) I am using now, Each line begins with a boxed question mark like this:
Update from discussion in comments on another answer. The problem described below applies to Word 2016 and earlier. Starting in Office 365 (and probably Word 2019, but that's not been confirmed) the Replace behavior has been changed to "convert" ANSI 13 to a "real" paragraph mark, so the problem in the question would not occur.
Answer
The reason for the odd formatting behavior is the use of Chr(11), which inserts a new line (Shift + Enter) instead of a new paragraph. So a paragraph style applied to any part of this text formats the entire text with the same style.
In this particular case (working with Replace), vbCr or the equivalent Chr(13) also don't work because these are not really Word's native paragraph. A paragraph is much more than just ANSI code 13 - it contains paragraph formatting information. So, while the code is running, Word is not really recognizing these as true paragraph marks and the paragraph style assignment is being applied to "everything".
What does work is to use the string ^p, which in Word's Find/Replace is the "alias" for a complete paragraph mark. So, for example:
replace_stuff "<PLACEHOLDER>", section_names(i) & "^p" & "<PLACEHOLDER>", wdStyleHeading2
replace_stuff "<PLACEHOLDER>", section_bodies(i) & "^p" & "<PLACEHOLDER>", wdStyleNormal
There is, however, a more efficient way to build a document than inserting a placeholder for each new item and using Find/Replace to replace the placeholder with the document content. The more conventional approach is to work with a Range object (think of it like an invisible selection)...
Assign content to the Range, format it, collapse (like pressing right-arrow for a selection) and repeat. Here's an example that returns the same result as the (corrected) code in the question:
Sub main()
Dim rng As Range
Set rng = ActiveDocument.content
Dim section_names(2) As String
section_names(0) = "Introduction"
section_names(1) = "Background"
section_names(2) = "Conclusion"
Dim section_bodies(2) As String
section_bodies(0) = "This is the body text for the introduction! Fetched from some file."
section_bodies(1) = "And Background... I have no issue fetching data from the files."
section_bodies(2) = "And for the conclusion... But I want the styles to 'stick'!"
Dim i As Integer
For i = 0 To 2
BuildParagraph section_names(i), wdStyleHeading2, rng
BuildParagraph section_bodies(i), wdStyleNormal, rng
Next i
End Sub
Sub BuildParagraph(para_text As String, para_style As Long, rng As Range)
rng.Text = para_text
rng.style = para_style
rng.InsertParagraphAfter
rng.Collapse wdCollapseEnd
End Sub
The problem is caused by your use of Chr(11) which is a manual line break. This results in all of the text being in a single paragraph. When the paragraph style is applied it applies to the entire paragraph.
Replace Chr(11) with vbCr to ensure that each piece of text is in a separate paragraph.

How to replace Fields in Word document with their content using VBA?

Some sites use textarea to publish code in articles. If someone copy/paste the article in Word, it shows empty textarea with scrollbars and below the code in a table with numbered lines.
I want to replace it with just code (or with just the table, which I can successfully convert to text), by removing the textarea.
Did try to do it like this
Sub RemoveTextBoxes()
Dim oFld As Word.FormField
With Application.ActiveDocument
' \\ TextInput Type requires to unprotect the document
If .ProtectionType <> wdNoProtection Then .Unprotect
' \\ Loop all formfields in active document
For Each oFld In .FormFields()
' \\ Only remove Formfield textboxes that have textinput only
If oFld.Type = wdFieldFormTextInput And oFld.TextInput.Type = wdRegularText Then
' \\ Delete
oFld.Delete
End If
Next
' \\ Reprotect the document
.Protect wdAllowOnlyFormFields, True
End With
End Sub
If I press Alt+F9 (displays field codes) I do see now
{ HTMLCONTROL Forms.HTML :TextArea.1 }
above the text box with scrollbars! If I close and open up again, it's still here.
How do I get this TextArea content and remove|replace the element with the content?
Dynamic content in Word is managed using "fields". Not all fields that accept input are "form fields", as you discovered when using Alt+F9 do display the field codes.
Word's Find / Replace functionality is quite powerful: it can also be used to find fields, even specific fields. In this case, since you simply want them removed, the HTMLControl fields can be found and replaced with "nothing". (If you want to be more specific and leave some HTMLControl fields, use as much text as necessary to remove only those fields.)
Many people don't realize it, but you can search field codes without needing to display them. Find can also work with field results displayed. The trick is to set the Range.TextRetrievalMode to include field codes (and, in this case, I think also inlcuding hidden text is a good idea, but if that's a problem, comment out or delete that line).
The ^d in the search text represents the opening field bracket: { - if this were left out only what is inside the brackets would be replaced (deleted), which I don't recommend. With ^d the entire field - including the closing bracket - is affected.
Sub FindAndDeleteHtmlFields()
Dim doc As word.Document
Dim fld As word.Field
Dim rngFind As word.Range
Set doc = ActiveDocument
Set rngFind = doc.content
rngFind.TextRetrievalMode.IncludeFieldCodes = True
rngFind.TextRetrievalMode.IncludeHiddenText = True
With rngFind.Find
.Text = "^d HTMLControl"
.ClearFormatting
.Replacement.Text = ""
.Execute Replace:=wdReplaceAll
End With
End Sub
Note that this also ports to C# - I have the impression that's actually where you're working...

Indent multiple lines the same way as plain text editor

Sometimes I have code blocks in my Word documents, and I want to work with them without copying to plain text editor.
Namely, I want to have an ability to indent/unindent multiple lines of code using "Tab" character. This task is very simple in any plain text editor or IDE, but for the sake of clarity, I will show it here. Tabs are shown as black arrows:
Initial state
Using the Shift key or mouse, I selected a part of JavaScript function
Then I pressed Tab key on my keyboard
Selected lines were indented by inserting tab character on each line.
How it could be done with VBA?
Since I don't post any code (as evidence of my own efforts), I don't expect to get something completely working. But at least, I hope to get an understanding "how" it could be done.
As David suggested, I recorded a macro. Here how it looks:
Sub Indentator()
Selection.TypeText Text:=vbTab
End Sub
The problem is, that I don't understand how to get it work for multiple lines. If I select them, this macro (and it was not surprise for me) just inserts "Tab" instead of selection.
Insert a tab character at the start of each paragraph in the selection:
Sub Indentator()
Dim para As Paragraph
For Each para In Selection.Paragraphs
para.Range.InsertBefore vbTab
Next
End Sub
(This assumes that each of your code "lines" is a new "paragraph" in Word, which it usually would be if you are intending to copy/paste this to/from actual code.)
If the macros are named IncreaseIndent and DecreaseIndent, they can be run using the Increase and Decrease Indent buttons on the Home tab.
Sub IncreaseIndent()
If Selection.Start = Selection.End Then
Selection.InsertBefore vbTab
Selection.Start = Selection.End
Else
Dim p As Paragraph
For Each p In Selection.Paragraphs
p.Range.InsertBefore vbTab
Next
End If
End Sub
Sub DecreaseIndent()
If Selection.Start = Selection.Paragraphs(1).Range.Start Then
Selection.Start = Selection.Start + 1
End If
Dim p As Paragraph, c As Range
For Each p In Selection.Paragraphs
Set c = p.Range.Characters(1)
If c.Text = vbTab Then c.Delete
Next
End Sub
Reference https://wordmvp.com/FAQs/MacrosVBA/InterceptSavePrint.htm

Insert highlighted sentence at bookmark in MS Word?

So I have a userform that I use to populate a template with text at certain bookmarks. To make it easy to see what text has been inserted, I would like it to be highlighted with yellow. Is there a convenient way to do this for every text that is inserted without typing it, selecting it and then highlighting it? As an example, this is what part of my code looks like atm:
With ActiveDocument
Options.DefaultHighlightColorIndex = wdYellow
'[highlight=yellow].Bookmarks("Modtager").Range.Text = TxtModtager.Value[/highlight]
.Bookmarks("Modtager").Range.Text = TxtModtager.Value
.Bookmarks("KSnr1").Range.Text = txtKSnr.Value
.Bookmarks("KSnr2").Range.Text = txtKSnr.Value
The first line doesn't seem to do anything - even without the option turned on new text is still not highlighted.
The second line is something I found at another site but had to be commented as it's not working.
The last three lines insert the actual text and I guess you could choose to select the bookmark first and then Selection.TypeText Text:="Whatever value I need", followed by selecting the new phrase again (how?) and choose .HighlightColorIndex = wdYellow.
There should be a better way though, any suggestions? :)
This works for me:
SetBkmkText "Modtager", TxtModtager.Value
SetBkmkText "KSnr1", txtKSnr.Value
SetBkmkText "KSnr2", txtKSnr.Value
Sub SetBkmkText(bkmk as String, NewText as String)
With ActiveDocument.Bookmarks(bkmk).Range
.Text = NewText
.HighlightColorIndex = wdYellow
End With
End Sub