Hi this is a follow up question from my previous question: Insert text to Word from Excel looping through drop down list
In addition to inserting text, I need to insert graphs for every region and copy the graph under each text. The graphs changes with the data for every region and is located next to the data table.
So the result has to look like this:
Text 1
Graph 1
Text 2
Graph 2 etc.
The code that inserts text (see from the previous question):
Sub Export()
Dim reg As Variant, col As String, txt As String
With ThisWorkbook.Sheets("Sheet 1")
For Each reg In Array("Region1", "Region2", "Region3")
.Range("B3") = reg
.Calculate
col = IIf(.Range("D2").Value = 14, "C", "D") 'select column due to D2 value
' collect all texts in txt
txt = txt & vbTab & "For " & reg & ", on June, 21 the estimate was " & _
.Range(col & "6").Text & " and the volume was " & .Range(col & "7").Text & _
" and the variance was " & .Range(col & "8").Text & vbLf
Next
End With
With CreateObject("Word.Application").Documents.Add
.Range.Text = txt ' output all text to the document
.SaveAs "C:\temp\AllTheText.docx" ' your path and name
.Parent.Quit 'quit Word
End With
End Sub
Sorry this isn't a bonafide answer, I don't have the reputation to ask a follow up question...
Have you considered copying the chart from Excel into Word, then issuing a Refresh command on the chart in Word once data is updated in Excel? You could then create a "AlltheTextTemplate" Word file with the charts preset. You'd then just create a copy of the template when creating a new report, fill it in with the data, and create a small bit of code to refresh the chart object.
Here's a few lines you could add:
Set wordapp = CreateObject("Word.Application")
' Path listed here assumes the template is stored in the same DIR as the workbook
Set objDoc = wordapp.documents.Open(Application.ThisWorkbook.Path & "\Template.docx")
wordapp.Visible = True 'Remove if you don't need to see it
With ObjDoc
.Range.Text = txt ' output all text to the document
.InlineShapes(1).LinkFormat.Update 'update the shape ID as needed
.InlineShapes(1).LinkFormat.BreakLink 'If you want to break the data link
.SaveAs "C:\temp\AllTheText.docx" ' your path and name
.Parent.Quit 'quit Word
End With
Related
I am trying to copy a bunch of text data from an excel spreadsheet into multiple separate word documents (one excel row = one document, but each column's heading has to be included before the text from the respective field of the row). I also want these documents' names to be the text from specific fields in the spreadsheet (row headers).
Because I want fancy formatting, and a specific order of copying/pasting things (not all fields are included), I am using a word template with bookmarks that I can feed into VBA. It fills the template well, but I cannot save it as a standard word document before repeating the loop. I get the error 'Object doesn't support this property or method'. Is there a way to overcome it, or a more elegant method I've missed?
Here is the code:
Sub Primitive()
Dim objWord As Object
Dim ws As Worksheet
Dim i As Integer
Set ws = ThisWorkbook.Sheets("Sheet1")
i = 2 ' First row to process
'Start of loop
Do Until ws.Range("B" & i) = ""
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
'Change to local path of template file
objWord.Documents.Open "C:path of template file.dotx"
With objWord.ActiveDocument
.Bookmarks("FirstBookmark").Range.Text = ws.Range("B" & i).Value & " " & ws.Range("C" & i)
.Bookmarks("headlineZ").Range.Text = ws.Range("Z1").Value
lots of this^ here to arrange the data right in the document
NewFileName = "C:\path of where I want the new file" & ws.Range("C" & i).Value & ".docx"
This is the line that gives the error 483:
objWord.SaveAs2 Filename:="NewFileName"
End With
objWord.Close
Set objWord = Nothing
i = i + 1
Loop
End Sub
You are trying to save Word, rather than the document. If you replace this
objWord.SaveAs2 Filename:="NewFileName"
with this
objWord.ActiveDocument.SaveAs2 Filename:="NewFileName"
it should work better. That said, you should not use ActiveDocument in your macros. Consider replacing
objWord.Documents.Open "C:path of template file.dotx"
with something like
Set TargetDocument = objWord.Documents.Open("C:path of template file.dotx")
and replace all ActiveDocument with TargetDocument.
i wrote the following code in VBA. I'm able to save the template onto the disk but the changes made are also made on the template which is then saved. I want to save the template with the information separately onto the disk and close the template without any changes being made to it. Also after I insert the details into header / footer, i used code to close the header / footer pane. That no longer works and now shows an extra page since i have separate header / footer for each page. How can i do this with the embedded word template since this worked if i kept the word template outside
Private Sub M114_Click()
Dim oleObject As Object
Dim wDoc As Object
Set oleObject = ActiveWorkbook.Sheets("Properties").OLEObjects(1)
oleObject.Verb Verb:=xlPrimary
ActiveSheet.Range("A1").Select
Set wDoc = oleObject.Object
' Creates the last row that will be used
'lRow = ThisWorkbook.Sheets("Input").Cells(Rows.Count, 1).End(xlUp).Row
' Loop through all the rows
'For i = 3 To lRow
i = 3
' Control 1/21 - Date of Letter
wDoc.ContentControls(1).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 2)
wDoc.ContentControls(21).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 2)
' Control 2/14 - Bank Contact Name
wDoc.ContentControls(2).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 13)
wDoc.ContentControls(14).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 13)
' Update Headers from page 3 to page 5
For j = 3 To 5
With wDoc.Sections(j).Headers(wdHeaderFooterPrimary).Range
.InsertAfter Text:=vbCrLf & UCase(ThisWorkbook.Sheets("Input").Cells(i, 6))
.InsertAfter vbTab
.InsertAfter Text:=vbCrLf & vbCrLf & UCase(ThisWorkbook.Sheets("Input").Cells(i, 7))
.InsertAfter vbTab
.InsertAfter Text:=vbCrLf & vbCrLf & ("At close of business on 31 December " & DatePart("yyyy", ThisWorkbook.Sheets("Input").Cells(i, 4)))
End With
Next j
'''' Issue with this resolve this
' Close the header / footer pane
wDoc.ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
wDoc.ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument
' Create the file name and save and close the file
file_name = Application.WorksheetFunction.Trim("BankConf-" & ThisWorkbook.Sheets("Input").Cells(i, 6) & "-" & ThisWorkbook.Sheets("Input").Cells(i, 7) & ".doc")
wDoc.SaveAs2 (ThisWorkbook.Path & "/" & file_name)
'wDoc.ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges
wDoc.Application.Quit
Side comment first: usually one would do what you want with mail merge in word ...
Regarding your question:
First of all - you should add a document (docx) as oleobject not a template (dotx). The template shows a somewhat strange behaviour.
Second it is necessary that you first do the saveAs, then open the new file to a separate doc-variable. By that the original oleObject-doc will not be edited.
Furthermore I would suggest two enhancements that make your code much more readable and more robust:
Insert a table for your data (= listobject in VBA) - then you can address the column-names in the code which is easier to maintain and to read than .cells(i,6).
You then can use these column names as tags for the content controls (ccs) in the word document. It is possible to name two different ccs with the same tag name. There is a method selectContentControlsByTag that returns all ccs with the same tag-name. Even those from headers and footers. So you should according ccs in the headers as well.
(Referencing a cc by index is critical as the index may change if you add a new cc or move them around or add text or ...)
As I understand you only insert some of the values to the letter. Therefore I suggest to postfix these column names e.g. by _cc.
This is the modified code - I added Microsoft Word as a reference to the VBA project.
Option Explicit
Sub createAll()
Dim docSource As Word.Document
Set docSource = getSourceDoc 'from oleObject
'assumption your data are in a table --> insert > table
'column names that have values that should go into the letter are named [CC-Tag]_CC
'example: columns name = DateOfLetter_CC | content controls tag= DateOfLetter
Dim lo As ListObject
Set lo = ThisWorkbook.Sheets("Input").ListObjects(1)
Dim lr As ListRow, lc As ListColumn
Dim docTarget As Word.Document, cc As ContentControl
'loop all rows of data table
For Each lr In lo.ListRows
'get empty word doc for this entry
Set docTarget = getTargetDoc(docSource, getFullFilename(lr))
For Each lc In lo.ListColumns
'within the word doc each CC has an according tag (without postfix)
If Right(lc.Name, 3) = "_CC" Then
For Each cc In docTarget.SelectContentControlsByTag(Split(lc.Name, "_")(0))
'there can be multiple CCs with the same tag
'tags within headers/footers are also handled within this loop
cc.Range.Text = lr.Range.Cells(1, lc.Index)
Next
End If
Next
docTarget.Close True
Next
'close Word
docSource.Application.Quit
MsgBox "ready"
End Sub
Private Function getFullFilename(lr As ListRow) As String
'you have to adjust this to your needs, i.e. add the correct column names to build the filename
Dim lo As ListObject
Set lo = lr.Parent
With lr.Range
getFullFilename = ThisWorkbook.Path & "\" & .Cells(1, lo.ListColumns("BankContactName_CC").Index).Value & ".docx"
End With
End Function
Private Function getSourceDoc() As Word.Document
'retrieves the oleDoc which is later used to save copies from
Dim oleObject As oleObject
Set oleObject = ThisWorkbook.Sheets("Properties").OLEObjects(1)
oleObject.Verb xlVerbOpen
Set getSourceDoc = oleObject.Object
End Function
Private Function getTargetDoc(docSource As Word.Document, FullFilename As String) As Word.Document
'saveas new file - open new file
'this is then returned
docSource.SaveAs2 FullFilename
Dim wrdApp As Word.Application
Set wrdApp = docSource.Application
Set getTargetDoc = wrdApp.Documents.Open(FullFilename)
End Function
I make entomological specimen labels that come with an embedded QR code. Museum curators can scan the QR codes of a series of specimens in the same group and manipulate data.
The QR code images are inserted as "shapes" (I believe--they respond to shape commands in the macro), generated via VBA code by Jiri Gabriel, with editing by Jonas Heidelberg (https://github.com/JonasHeidelberg/barcode-vba-macro-only).
The macro takes data, populates cells with strings and values (i.e., what gets printed on the human-readable part of the individual labels). When all of the printed text is inserted, the macro iteratively generates one QR code image at a time and places each generated image next to the corresponding human-readable label.
The macro is quick to generate and insert the first few QR code images then gets progressively slower. I presume because Excel is not built to handle a large number of high-resolution images on the same spreadsheet. My sheet design accommodates 220 individual QR code images, but it takes nearly 10 minutes to populate the spreadsheet with 50 QR code images (it takes less than 30 seconds to populate 10 QR code images, so the slowdown is appreciable).
I have tried:
Disable screen updating - does not seem to improve the processing speed
Set calculation to manual - does not seem to improve the processing speed
After generating each QR code image, hide the image by using the following code, and then at the very end, turn all the images visible - seems to help a little bit but not nearly sufficient to make the macro usable at scale.
ActiveSheet.Shapes.SelectAll
Selection.ShapeRange.Visible = False
I looked for solutions to see if the QR code image shapes can be merged into one shape, because after all, wouldn't it be easier to manage a single shape than 200+ individual small shapes? There seems to be no functionality to combine all of the shapes into a single shape.
Another solution I thought about is simultaneously generating all of the QR codes, instead of iteratively, then perhaps it won't have the issue of the later-coming shapes being slow to render due to having to hold all of the previously rendered codes in its memory. I haven't found a way to write the code such that all QR code image shapes are generated in parallel, rather than in sequence.
Another solution I toyed with is to paste the shapes as PNG or some other image that could potentially be easier to deal with, but I get a lot of loss of quality, which seems strange because the QR code should be just a matrix of black and white cells, right? Why do they lose so much quality?
I would suggest an approach based on built in MS Word 2013+ feature (https://support.microsoft.com/en-us/office/field-codes-displaybarcode-6d81eade-762d-4b44-ae81-f9d3d9e07be3). Below is an example of generating 200 QR codes in 10.6 seconds:
Option Explicit
Sub MakeQRcodes()
Const QR_COUNT = 200
Dim fld As Field, tbl As Table, rng As Range
Dim Code As String, i As Integer, t As Single
t = Timer
ThisDocument.Range.Delete
Set tbl = ThisDocument.Tables.Add(Range:=Selection.Range, NumRows:=QR_COUNT, NumColumns:= _
2, DefaultTableBehavior:=wdWord9TableBehavior, AutoFitBehavior:= _
wdAutoFitFixed)
For i = 1 To QR_COUNT
Code = "Insect #" & i ' data can be obtained from Excel spreadsheet
tbl.Cell(i, 1).Range.Text = "QR code for [" & Code & "]:"
Set rng = tbl.Cell(i, 2).Range
rng.Collapse Direction:=wdCollapseStart
Set fld = ThisDocument.Fields.Add(Range:=rng, Type:=wdFieldEmpty, _
Text:="DisplayBarcode """ & Code & """ QR \q 3")
Next i
ThisDocument.ActiveWindow.View.ShowFieldCodes = False
With tbl.Range
.ParagraphFormat.Alignment = wdAlignParagraphCenter
.Cells.VerticalAlignment = wdCellAlignVerticalCenter
End With
Debug.Print "Done " & QR_COUNT & " items in " & Timer - t & " seconds"
End Sub
' Done 200 items in 10,62109 seconds
Result:
Edit2 (VBA Excel code)
Please note that in my experience the DisplayBarcode field works well only with the Latin alphabet. If you have other symbols, check the code on real lines.
Option Explicit
Sub makeQRs()
Dim arr
arr = ThisWorkbook.Sheets("Sheet1").ListObjects("Table1").DataBodyRange.Columns(3)
Call MakeQRcodes(arr)
End Sub
Sub MakeQRcodes(arr) ' arr(n,1)
'you need to add a reference to the "Microsoft Word Object Library" in the Tools-References VBE menu
Dim wd As New Word.Application, doc As Word.Document, fld As Word.Field, tbl As Word.Table, rng As Word.Range
Dim Code As String, i As Integer, QR_count As Integer, t As Single
QR_count = UBound(arr, 1)
t = Timer
wd.Visible = False ' hide the Word app
Set doc = wd.Documents.Add ' create a new Word document
Set tbl = doc.Tables.Add(Range:=doc.Range, NumRows:=QR_count, _
NumColumns:=2, DefaultTableBehavior:=1) 'wdWord9TableBehavior = 1
For i = 1 To QR_count
Code = arr(i, 1)
tbl.Cell(i, 1).Range.Text = "QR code for [" & Code & "]:"
Set rng = tbl.Cell(i, 2).Range
rng.Collapse Direction:=wdCollapseStart
Set fld = doc.Fields.Add(Range:=rng, Type:=-1, _
Text:="DisplayBarcode """ & Code & """ QR \q 3")
Next i
doc.ActiveWindow.View.ShowFieldCodes = False
With tbl.Range ' center text and QR-code in the table cells
.ParagraphFormat.Alignment = 1 'wdAlignParagraphCenter
.Cells.VerticalAlignment = 1 'wdCellAlignVerticalCenter
End With
Application.DisplayAlerts = 0 'wdAlertsNone
With doc
' save the Word doc as .pdf in the same folder as this Excel workbook
.SaveAs2 ThisWorkbook.Path & "\QR.pdf", 17 'wdFormatPDF
.Close False ' close Word document without saving
wd.Quit ' close Word app
End With
Application.DisplayAlerts = -1 'wdAlertsAll
MsgBox "Done " & QR_count & " QR-codes in " & Round(Timer - t, 1) & " seconds," & vbLf _
& "saved in " & ThisWorkbook.Path & "\QR.pdf"
End Sub
Data & result MsgBox
QR.pdf
I have Excel Workbook from where I am running following code below. I have logo and page numbering already in Word document so I do not need to paste the whole range from Excel. I have two Text Boxes where data from spreadsheet should be inserted.
I need to copy Worksheets("Other Data").Range("A58:A60") and paste it to "Text Box 1" that I have in Word documents header. Three sentances on different rows. Text Box should be wrapped?
I need to copy Worksheets("Other Data").Range("A68") and paste it to "Text Box 2" that I have in Word documents header. One sentance.
AutoFitWindows doesn't work. There have to be something with variables but I can't figure what exactly is wrong. Tried different ways with no success.
Here is my code:
Sub excelToWord_click()
Dim head As Excel.Range
Dim foot As Excel.Range
Dim WordTable As Word.Table
Set wdApp = CreateObject("Word.Application")
wdApp.Documents.Open FileName:=ThisWorkbook.Path & "\" & "MyDOC" & ".docx"
wdApp.Visible = True
Set head = ThisWorkbook.Worksheets("Other Data").Range("A58:A60")
head.Copy
'|| I need to paste copied cells to "Text Box 1" in my Word document ||'
With wdApp.ActiveDocument.Sections(1)
.Headers(wdHeaderFooterIndex.wdHeaderFooterPrimary).Range.Shapes("Text Box 1").Activate
head.Paste
End With
'|| ---------------------------------------------------------------- ||'
Set head2 = ThisWorkbook.Worksheets("Other Data").Range("A68")
head2.Copy
'|| I need to paste copied cells to "Text Box 2" in my Word document ||'
With wdApp.ActiveDocument.Sections(1)
.Headers(wdHeaderFooterIndex.wdHeaderFooterPrimary).Range.Shapes("Text Box 2").Activate
head2.Paste
End With
'|| ---------------------------------------------------------------- ||'
Set foot = ThisWorkbook.Worksheets("Other Data").Range("A62:H65")
foot.Copy
With wdApp.ActiveDocument.Sections(1)
.Footers(wdHeaderFooterIndex.wdHeaderFooterPrimary).Range.Paste
End With
'|| Autofit table to page in Footer ||'
WordTable.AutoFitBehavior (wdAutoFitWindow)
'|| ---------------------------------------------------------------- ||'
'restore Word
If wdApp.ActiveWindow.View.SplitSpecial <> 0 Then
wdApp.ActiveWindow.Panes(2).Close
End If
If wdApp.ActiveWindow.ActivePane.View.Type = 1 _
Or wdApp.ActiveWindow.ActivePane.View.Type = 2 Then
wdApp.ActiveWindow.ActivePane.View.Type = 3
End If
wdApp.WordBasic.AcceptAllChangesInDoc
'wdApp.ActiveDocument.PrintOut, Copies:=1
wdApp.ActiveDocument.ExportAsFixedFormat outputfilename:=ThisWorkbook.Path & "\" & Sheets("MAIN").Range("D14").Value & ", " & Sheets("MAIN").Range("D11").Value & "_" & "Document" & "_" & ".pdf", exportformat:=wdExportFormatPDF
wdApp.ActiveDocument.SaveAs ThisWorkbook.Path & "\" & Worksheets("MAIN").Range("D14").Value & ", " & Worksheets("MAIN").Range("D11").Value & "_" & "Document" & "_" & ".docx"
wdApp.Quit '<--| quit Word
Set wdApp = Nothing '<--| release object variable
'wdApp.ActiveWindow.Close savechanges:=False
End Sub
Your problem is because you are late binding your word application object rather than installing the Word reference to the VBA IDE.
This means that any references to word constants without qualification to the variable you are using for your word app will be interpreted as the default (0 or Null) value.
The simplest way to resolve this issue is in the VBA IDE; goto Tools.References and make sure that the check box next to Microsoft Word ...... is ticked.
If you would prefer to qualify your variables then you need to change word constants so that they are prefixed with WdApp, your variable for the Word Application.
e.g. wdApp.wdHeaderFooterIndex.wdHeaderFooterPrimary
With the Word reference installed you can just say
wdHeaderFooterPrimary.
I need to copy name and last name from Excel, paste it into a Word template and print. I have an Excel file from which I need to copy two columns from each row (i.e. E31:F31,E40:F40) into a bookmark in Word and then print it.
I need to loop either from row X to Y or X number of times. The Excel file is not well formatted. I managed to apply paste&print to a custom number of cells, but I get an error probably because I try to do it all at once.
document is in use do you want to open a temp copy
Copied text shows up in Word with big gaps between two words (what came from columns E and F). How do I fix?
Sub Copy_Excel_Cell_to_Word_Form()
Dim wdApp As Object 'Word.Application
Dim wdDoc As Object 'Word.Document
For i = 31 To 60
'Open new instance of Microsoft Word
Set wdApp = CreateObject("Word.Application")
'Make application visible
'wdApp.Visible = False
'Open the word document
Set wdDoc = wdApp.Documents.Open("\\example.doc")
'Copy value
Worksheets(1).Range("E" & i, "F" & i).Copy
'Paste to word document
wdDoc.Bookmarks("WORKER").Range.PasteAndFormat (wdFormatPlainText)
wdDoc.PrintOut
Next
End Sub
For your second point, try this:
Declare a string variable called workerName. Instead of Worksheets(1).Range("E" & i, "F" & i).Copy use
workerName = Worksheets(1).Cells(i,5).Value 'e = 5
workerName = workerName & " " & Worksheets(1).Cells(i,6).Value & vbCr 'f = 6
And instead of use wdDoc.Bookmarks("WORKER").Range.PasteAndFormat (wdFormatPlainText) use
wdDoc.Bookmarks("WORKER").Range.Text = workerName
This should clear up the big gaps.