VBA Using Excel 2016 to Populate Word 2016 Content Controls - excel

I'm attempting to populate a Word.dotm document that has content control text boxes using Excel.
In Excel, I create the word objects and open the file, but I can't figure out how to actually access the controls.
In the Word doc's VBA, I can access them via
.SelectContentControlsByTitle("control").Item(1).Range.Text = "..."
but that doesn't work using an Object in Excel..
Here's the code I've tried in Excel:
Dim objWord As Object
Dim objDoc As Object
Dim objSelection As Object
Dim ctrl As Word.ContentControl
Set objWord = CreateObject("Word.Application")
Set objDoc = objWord.Documents.Add(ThisWorkbook.Path & "\MyDoc.dotm")
objWord.Visible = True
Set objSelection = objWord.Selection
'Doesn't work
objDoc.SelectContentControlsByTitle("control").Item(1).Range.Text = "..."
'Doesn't work either, but tried nonetheless
For Each ctrl In objDoc.contentcontrols
If ctrl.Title = "control" Then
ctrl.Range.Text = "!"
Exit For
End If
Next ctrl
UPDATE:
I didn't have the MS Word 16.0 Object Library referenced... the early binding and the tip to copy the template file rather than altering it directly mentioned by Timothy Rylatt now works and John Korchok's For Each loop works as well. Their additions have been added to the code above. Thank you!

Although you have declared ctrl as a ContentControl it is not a Word content control, which is why the For Each loop won't work.
If you want to use early binding you need to specify the library that the object type belongs to in the variable declaration. Some libraries have objects with the same name but which are completely different.
Dim ctrl As Word.ContentControl
NB: You should also create a document from the Word template rather than edit the template directly:
Set objDoc = objWord.Documents.Add(ThisWorkbook.Path & "\MyDoc.dotm")

In any macro, but especially in cross-program macros, avoid using the Selection object and use Range object instead. Sometimes using the range object requires a slightly less direct route to accessing the control, such as having to poll all content controls to find which one has the correct title:
Sub SetCCText()
Dim CC As ContentControl
For Each CC In ActiveDocument.ContentControls
If CC.Title = "control" Then
CC.Range.Text = "Text"
End If
Next CC
End Sub

Related

move selection to end of word document

i have a word document that has text in it, when the document opens the selection is in the very first line of the document so when I run the code below to add a new page the whole text moves to the new page, how do I move the selection to the end of the document so that when I add a new page the text does not move to the new page?
edit: tried below but does not work
"objSelection.EndKey Unit:=wdStory, Extend:=wdMove"
"objWord.Documents("letters.docx").Selected.EndKey Unit:=wdStory, Extend:=wdMove"
Sub exceltoword()
Dim objWord As Object
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
objWord.Documents.Open "C:\Users\WORK\Desktop\letters.docx"
objWord.Activate
Set objSelection = objWord.Selection
'**MOVE TO END OF DOCUMENT BEFORE ADDING NEW PAGE**
'tried objSelection.EndKey Unit:=wdStory, Extend:=wdMove --- does not work
'tried objWord.Documents("letters.docx").Selected.EndKey Unit:=wdStory, Extend:=wdMove --- does not work
objSelection.InsertNewPage
objWord.Application.Quit
Set objWord = Nothing
End Sub
Your issue is a result of your use of late binding, declaring objWord As Object instead of setting a reference to the Word object library.
When using late binding you cannot use the enums or constants from the Word object library as Excel doesn't know what those represent. You either have to declare those constants yourself or use their underlying values.
Actually there is little value to using late binding in this instance. The only advantage to late binding is version independence, i.e. you can write code in a newer version and it will theoretically work in an older one. But that advantage is lost unless you also use late binding for the host application code as Office is usually installed as a package. It is better to use early binding when working with other Office applications and reserve late binding for other commonly used libraries, e.g. ADO or XML.
If you want to know more about late binding see https://rubberduckvba.wordpress.com/2019/04/28/late-binding/
I also noticed that you are not using Option Explicit at the top of the code module, as your code contains undeclared variables. To add this automatically to new modules open the VBE and go to Tools | Options. In the Options dialog ensure that Require Variable Declaration is checked.
You should also break the habit of using Selection. There is rarely any need to select anything when using VBA.
So your code should look similar to this:
Sub exceltoword()
Dim wdApp As Word.Application
Set wdApp = CreateObject("Word.Application")
wdApp.Visible = True
Dim wdDoc As Word.Document
Set wdDoc = wdApp.Documents.Open("C:\Users\WORK\Desktop\letters.docx")
wdDoc.Characters.Last.InsertBreak Type:=wdPageBreak
wdApp.Quit
Set wdApp = Nothing
End Sub

Save pictures of Excel cells into a Word document

My code pastes a picture of the cells.
It should then paste the other cells which don't fit on the first page.
The picture of the 'second page' does not paste.
Dim wdApp As Object
Dim wd As Object
Dim sFil As String
On Error Resume Next
Set wdApp = GetObject(, "Word.Application")
If Err.Number <> 0 Then Set wdApp = CreateObject("Word.Application")
On Error GoTo 0
Set wd = wdApp.Documents.Add ' create a new document
wdApp.Visible = True
'change sheet and range below
ActiveSheet.Range("a1:z43").CopyPicture xlPrinter
wd.Range.Paste
wdApp.ActiveDocument.Range(0, 0).InsertBreak Type:=wdSectionBreakNextPage
wdApp.ActiveDocument.Sections (2)
ActiveSheet.Range("a43:z76").CopyPicture xlPicture
wd.Range.Paste
'wd.SaveAs Filename:="I:\'Files\RKG-COMMS\" & URNa & " - " & URNb & ".doc"
'wd.Close
'wd.Quit
Here is the result - only the first page
This is the second page which should be pasted
It's difficult for me to test what you're doing, but logically I believe the following should work, although there may be one problem (keep reading)...
Notice how this code uses a Word Range object as the target for new content. After pasting it's uncertain whether docRange will be before the pasted content, or contain the pasted content. If it contains the pasted content, the code should work. If it's before the pasted content, then next picture will come in before the first one. In that case, repeat the line Set docRange = wd.Content immediately after docRange.Paste.
Set wd = wdApp.Documents.Add ' create a new document
Dim docRange as Object 'Word.Range
Set docRange = wd.Content
wdApp.Visible = True
'change sheet and range below
ActiveSheet.Range("a1:z43").CopyPicture xlPrinter
docRange.Paste
'Go to the end
docRange.Collapse 0 'wdCollapseEnd
docRange.InsertBreak Type:=wdSectionBreakNextPage
ActiveSheet.Range("a43:z76").CopyPicture xlPicture
docRange.Paste
In response to an inquiry in comments about how the code works in detail:
Dim docRange as Object 'Word.Range
Declares an object variable to hold the part of the document the code works with. Because the code in the question, running from Excel, apparently uses late-binding (no reference to the Word object library) it's declared as an Object.
If early-binding were being used (the code project has a reference to the Word libraray) then declaring it As Word.Range would be preferable. I include the Word object data type (Word.Range) for the sake of completeness and to make it possible to research the object and its properties in the Help.
Set docRange = wd.Content
This instantiates the Word.Range object to contain the entire main body of the document (wd being instantiated in the code in the question to the target Word document).
After pasting, docRange still refers to the entire content of the document, including what was pasted. If the section break and second paste action were to be executed immediately, this would delete the content of the range. (Think of it like selecting text in Word, then typing: what was selected is replaced by what is typed. In order to avoid this, one presses the right arrow key before typing so that the new content follows what was selected.)
For this reason, the range is "collapsed" (like pressing the arrow key). Then the section break is inserted and the next paste action is executed.

How do I copy the contents of one word document to the end of another using vba?

Goal for my project:
I want to be able to copy the contents of one document and append that selection to the end of another document.
What it does... (this is just background info so you understand why I am trying to do this):
I am trying to dynamically produce a document which quotes a variety of information regarding different parts and materials involved for a product.
The document itself has a consistent format which I have broken down and separated into two documents. The first contains a bunch of data that needs to be entered manually, and is where I want to append all additional content. The second contains roughly a dozen custom fields which are updated from an excel spreadsheet in VBA. For a single part and as a single doc this works as I want it (my base case). However my issue is when there are multiple parts for a project.
The Problem:
For multiple parts I have to store information in an array which changes in size dynamically as each additional part is added. When someone has added all the necessary parts they can select a button called "Create Quote".
Create quote runs a procedure which creates/opens separate copies of the two template documents mentioned above (saved on my computer). It then iterates through the array of parts and updates all the custom field in the 2nd document (no problems). Now I just need the contents of the 2nd document appended to the end of the first which is my problem.
What I want:
Ideally, my procedure will continue to iterate through every part in the array - updating custom fields, copy then paste the updated text, repeat... Until every part is included in the newly generated quote.
What I Tried - this code can be found in my generate quote procedure
I have tried many of the examples and suggestions provided by people who had similar question, but I don't know if its because I am operating from an excel doc, but many of their solution have not worked for me.
This is my most recent attempt and occurs after each iteration of the for loop
wrdDoc2.Fields.Update 'Update all the fields in the format document
wrdDoc2.Activate
Selection.WholeStory ' I want to select the entire document
Selection.Copy ' Copy the doc
wrdDoc1.Activate ' Set focus to the target document
Selection.EndKey wdStory ' I want the selection to be pasted to the end of the document
Selection.PasteAndFormat wdPasteDefault
QUOTE PROCEDURE - I am only including a handful of the fields I am updating because its not necessary to show them all
Private Sub quote_button_Click()
On Error GoTo RunError
Dim wrdApp1, wrdApp2 As Word.Application
Dim wrdDoc1, wrdDoc2 As Word.Document
Set wrdApp1 = CreateObject("Word.Application")
Set wrdApp2 = CreateObject("Word.Application")
wrdApp1.Visible = True
wrdApp2.Visible = True
Set wrdDoc1 = wrdApp1.Documents.Add(Template:="C:\MWES\AQT_v1.1(start).docm", NewTemplate:=False, DocumentType:=0)
Set wrdDoc2 = wrdApp2.Documents.Add(Template:="C:\MWES\AQT_v2.1(format).docm", NewTemplate:=False, DocumentType:=0)
Dim propName As String
For i = LBound(part_array, 1) To UBound(part_array, 1)
For Each prop In wrdDoc2.CustomDocumentProperties
propName = prop.name
' Looks for and sets the property name to custom values of select properties
With wrdDoc2.CustomDocumentProperties(propName)
Select Case propName
Case "EST_Quantity"
.value = part_array(i, 0) ' "QTY" ' Sheet1.Cells(1, 3) 'NA
Case "EST_Metal_Number"
.value = part_array(i, 1) ' "METAL_#" ' Sheet1.Cells(2, 3) 'NA"
Case "EST_Metal_Name"
.value = part_array(i, 2) ' "METAL_N" ' Sheet1.Cells(5, 2)
End Select
End With
Next prop ' Iterates until all the custom properties are set
wrdDoc2.Fields.Update 'Update all the fields in the format document
wrdDoc2.Activate
Selection.WholeStory ' I want to select the entire document
Selection.Copy ' Copy the doc
wrdDoc1.Activate ' Set focus to the target document
Selection.EndKey wdStory ' I want the selection to be pasted to the end of the document
Selection.PasteAndFormat wdPasteDefault
Next i ' update the document for the next part
RunError: ' Reportd any errors that might occur in the system
If Err.Number = 0 Then
Debug.Print "IGNORE ERROR 0!"
Else
Dim strError As String
strError = "ERROR: " & Err.Number & vbCrLf & Err.Description & vbCrLf & Erl
MsgBox strError
Debug.Print strError & " LINE: " & Erl
End If
End Sub
I apologize this was so long winded. Let me know if there is anything confusing or you may want clarified. I think I included everything though.
I think you're close, so here are a couple of comments and an example.
First of all, you're opening two separate MS Word Application objects. You only need one. In fact, it's possible that the copy/paste is failing because you're trying to copy from one Word app to a document opened in the other. (Trust me, I've seen weird things like this.) My example below shows how to do this by only opening a single application instance.
Dim mswApp As Word.Application
Set mswApp = AttachToMSWordApplication() 'more on this function below...
Dim doc1 As Word.Document
Dim doc2 As Word.Document
Set doc1 = mswApp.Documents.Open("C:\Temp\combined.docx")
Set doc2 = mswApp.Documents.Open("C:\Temp\control.docx")
While I don't often write code for Word, I've found that there are so many different ways to get at the same content using different objects or properties. This is always a source of confusion.
Based on this answer, which has worked well for me in the past, I then set up the source and destination ranges to perform the "copy":
Dim destination As Word.Range
Dim source As Word.Range
Set source = doc1.Content
Set destination = doc2.Content
destination.Collapse Direction:=Word.wdCollapseEnd
destination.FormattedText = source
Here is the whole module for reference:
Option Explicit
Sub AddDocs()
Dim wordWasRunning As Boolean
wordWasRunning = IsMSWordRunning()
Dim mswApp As Word.Application
Set mswApp = AttachToMSWordApplication()
Dim doc1 As Word.Document
Dim doc2 As Word.Document
Set doc1 = mswApp.Documents.Open("C:\Temp\combined.docx")
Set doc2 = mswApp.Documents.Open("C:\Temp\control.docx")
Dim destination As Word.Range
Dim source As Word.Range
Set source = doc1.Content
Set destination = doc2.Content
destination.Collapse Direction:=Word.wdCollapseEnd
destination.FormattedText = source
doc2.Close SaveChanges:=True
doc1.Close
If Not wordWasRunning Then
mswApp.Quit
End If
End Sub
Here's the promised note on a couple functions I use in the sample. I've built up a set of library functions, several of which help me access other Office applications. I save these modules as .bas files (by using the Export function in the VBA Editor) and import them as needed. So if you'd like to use it, just save the code below in using a plain text editor (NOT in the VBA Editor!), then import that file into your project.
Suggested filename is Lib_MSWordSupport.bas:
Attribute VB_Name = "Lib_MSWordSupport"
Attribute VB_Description = "Variety of support functions operating on MS Word"
Option Explicit
Public Function IsMSWordRunning() As Boolean
Attribute IsMSWordRunning.VB_Description = "quick check to see if an instance of MS Word is running"
'--- quick check to see if an instance of MS Word is running
Dim msApp As Object
On Error Resume Next
Set msApp = GetObject(, "Word.Application")
If Err > 0 Then
'--- not running
IsMSWordRunning = False
Else
'--- running
IsMSWordRunning = True
End If
End Function
Public Function AttachToMSWordApplication() As Word.Application
Attribute AttachToMSWordApplication.VB_Description = "finds an existing and running instance of MS Word, or starts the application if one is not already running"
'--- finds an existing and running instance of MS Word, or starts
' the application if one is not already running
Dim msApp As Word.Application
On Error Resume Next
Set msApp = GetObject(, "Word.Application")
If Err > 0 Then
'--- we have to start one
' an exception will be raised if the application is not installed
Set msApp = CreateObject("Word.Application")
End If
Set AttachToMSWordApplication = msApp
End Function

Having troubles creating and formatting Word tables from Excel VBA

I'm new to MS Word VBA and am having troubles with manipulating Word documents from Excel.
The Biggest problem so far is: codes that work in Word VBA just don't work in Excel. Very strange and frustrating.
Below are the codes:
Sub abc()
Dim MSWordApp As Object, MSWordDoc As Object
Set MSWordApp = CreateObject("Word.Application")
Set MSWordDoc = MSWordApp.Documents.Add
MSWordApp.Visible = True
With MSWordDoc
With .PageSetup
.TopMargin = Application.CentimetersToPoints(0.51)
.BottomMargin = Application.CentimetersToPoints(0.51)
.LeftMargin = Application.CentimetersToPoints(0.51)
.RightMargin = Application.CentimetersToPoints(0.51)
End With
.Tables.Add Range:=.Range(0, 0), NumRows:=3, NumColumns:=2
With .Tables(1)
.Rows.Alignment = wdAlignRowCenter
.Rows.HeightRule = wdRowHeightExactly
.Rows.Height = Application.CentimetersToPoints(9.55)
.Columns.PreferredWidthType = wdPreferredWidthPoints
.Columns.PreferredWidth = Application.CentimetersToPoints(9.9)
End With
End With
MSWordApp.Activate
Set MSWordApp = Nothing
Set MSWordDoc = Nothing
End Sub
These codes work perfectly in MS Word (Of course I've changed the names of object etc when I use them in MS Word).
However, weird things happen in Excel:
1) ".Rows.Alignment = wdAlignRowCenter" just don't work at all. The Rows.Alignment of the Word table just remains as default.
2) ".Columns.PreferredWidthType = wdPreferredWidthPoints" causes error in Excel. It works fine in Word; though in Excel, an empty error msg will pop up when every time I call this property. Have no idea why...
When controlling Microsoft Word from Excel VBA, you need to add a reference to the Microsoft Word Object Library. To do so, make sure you are on your module in the VBA window, and then click Tools then References.... In the popup, scroll down to find "Microsoft Word XX.X Object Library". The version # will vary based on what you have installed.
If it doesn't show in the list, you can find it on your hard drive, by clicking "Browse..." and navigating to the program files folder where MS Word is installed, then selecting the file called "MSWORD.OLB".
Since your code is written for use with late binding, you should NOT be adding a reference to Word. Instead, you need to either define or replace the Word constants you're using. For example, instead of:
.Rows.Alignment = wdAlignRowCenter
.Rows.HeightRule = wdRowHeightExactly
you could use:
.Rows.Alignment = 1 '1=wdAlignRowCenter
.Rows.HeightRule = 2 '2=wdRowHeightExactly
Alternatively, after:
Dim MSWordApp As Object, MSWordDoc As Object
you would insert:
Const wdAlignRowCenter as Long = 1: Const wdRowHeightExactly as Long = 2
Otherwise, if you're going to set a reference to Word, you should make your code consistent with early binding throughout. For example, instead of:
Dim MSWordApp As Object, MSWordDoc As Object
Set MSWordApp = CreateObject("Word.Application")
Set MSWordDoc = MSWordApp.Documents.Add
you might use:
Dim MSWordApp As New Word.Application, MSWordDoc As Word.Document
Set MSWordDoc = MSWordApp.Documents.Add

Macro using Documents.Open doesn't return a Document, but a String.

I want to open and read paragraphs of a Word document from an Excel macro. Here's the sample code that confuses me:
Sub example()
Dim docPath
docPath = "C:\temp\test.docx"
Dim wordApp
Set wordApp = CreateObject("Word.Application")
Dim doc
doc = wordApp.Documents.Open(docPath)
MsgBox (TypeName(doc)) 'This line displays String. According to the documentation it should be a Document.
End Sub
According to the documentation the Documents.Open method should return a Document that you can operate on. But when I check the type it says it has returned a String. Obviously I can't loop through the paragraphs of a string:
https://msdn.microsoft.com/en-us/vba/word-vba/articles/documents-open-method-word
Is the documentation wrong? Am I doing something wrong? More importantly, how do I loop through the paragraphs of a Word document from an Excel macro?
I'm using Office 365 if that matters and I have created a small Word document at C:\temp\test.docx.
try:
Set doc = wordApp.Documents.Open(docPath)
The String you are getting is some property of the Object you have created; might be the Name of the Object.................insert MsgBox doc to see what it is.
Although basically what is missing is a 'Set Doc = ...' you are guaranteed to get into trouble with a code like that. To prevent that some suggestions and a starter code (your code really, a little bit modified):
Add Word object reference from Tools\References and "type" your objects. Typing them would make it much easier to write code with intellisense support.
Never ever let the word object hang out there. Get rid of it when you are done.
Sample:
Dim docPath
docPath = "C:\temp\test.docx"
Dim wordApp As Word.Application
Set wordApp = CreateObject("Word.Application")
Dim doc As Word.Document
Set doc = wordApp.Documents.Open(docPath)
For i = 1 To doc.Paragraphs.Count
Cells(i, 1) = doc.Paragraphs(i).Range.Words.Count
Cells(i, 2) = doc.Paragraphs(i)
Next
wordApp.Quit
Set doc = Nothing
Set wordApp = Nothing

Resources