Mailmerge MailFormat and alighnment issues - vba

I have never used VBA for mailmerge before and recently inherited a docm created a few years ago. My two issues are:
1. How do I get the email to be sent as HTML? Have tried wdMailFormatHTML but it does not work.
2. The data source is in an excel file with headers. The "table" header does not align with the text below. What I want is for the header to adjust width to match the data below. Have tried numerous ways to fix the alignment within the document but to no avail. Also tried to add Column width to the code but I am probably doing it wrong as nothing seem to be working.
Below is the original code. Would appreciate if someone could help.
Sub RunMerge()
Application.ScreenUpdating = False
Dim Doc1 As Document, Doc2 As Document, Doc3 As Document, StrDoc As String
Set Doc1 = ThisDocument
StrDoc = ThisDocument.Path & "\EmailDataSource.doc"
If Dir(StrDoc) <> "" Then Kill StrDoc
With Doc1.MailMerge
If .State = wdMainAndDataSource Then
.Destination = wdSendToNewDocument
.Execute
Set Doc2 = ActiveDocument
End If
End With
Call EmailMergeTableMaker(Doc2)
With Doc2
.SaveAs FileName:=StrDoc, AddToRecentFiles:=False, FileFormat:=wdFormatDocument
StrDoc = .FullName
.Close
End With
Set Doc2 = Nothing
Set Doc3 = Documents.Open(FileName:=Doc1.Path & "\Email Merge Main Document.doc", _
AddToRecentFiles:=False)
With Doc3.MailMerge
.MainDocumentType = wdEMail
.OpenDataSource Name:=StrDoc, ConfirmConversions:=False, ReadOnly:=False, _
LinkToSource:=True, AddToRecentFiles:=False, Connection:="", SQLStatement:="", _
SQLStatement1:="", SubType:=wdMergeSubTypeOther
If .State = wdMainAndDataSource Then
.Destination = wdSendToEmail
.MailAddressFieldName = "Recipient"
.MailSubject = "TrackView follow-up - Missing timesheets/approvals"
.MailFormat = wdMailFormatPlainText
.Execute
End If
End With
Doc3.Close SaveChanges:=False
Set Doc3 = Nothing
Application.ScreenUpdating = True
End Sub
Sub EmailMergeTableMaker(DocName As Document)
Dim oTbl As Table, i As Integer, j As Integer, oRow As Row, oRng As Range, strTxt As String
With DocName
.Paragraphs(1).Range.Delete
Call TableJoiner
For Each oTbl In .Tables
j = 2
With oTbl
i = .Columns.Count - j
For Each oRow In .Rows
Set oRng = oRow.Cells(j).Range
With oRng
.MoveEnd Unit:=wdCell, Count:=i
.Cells.Merge
strTxt = Replace(.Text, vbCr, vbTab)
On Error Resume Next
If Len(strTxt) > 1 Then .Text = Left(strTxt, Len(strTxt) - 2)
End With
Next
End With
Next
For Each oTbl In .Tables
For i = 1 To j
oTbl.Columns(i).Cells.Merge
Next
Next
With .Tables(1)
.Rows.Add BeforeRow:=.Rows(1)
.Cell(1, 1).Range.Text = "Recipient"
.Cell(1, 2).Range.Text = "Data"
End With
.Paragraphs(1).Range.Delete
Call TableJoiner
End With
Set oRng = Nothing
End Sub
Private Sub TableJoiner()
Dim oTbl As Table
For Each oTbl In ActiveDocument.Tables
With oTbl.Range.Next
If .Information(wdWithInTable) = False Then .Delete
End With
Next
End Sub

Use the HTMLBody property of the mailitem
Dim OutMail As Object
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.Attachments.Add
.body = ""
.CC = ""
.HTMLBody = ""
.subject = ""
.to = emailTo
.Send
End With
On Error GoTo 0
Set OutMail = Nothing

There are at least two potential problems here.
One is that the wdMailFormatHTML parameter will only work with the full version of Outlook, not Outlook Express, etc. etc., i.e. Outlook must be the default email client on the relevant system for this to work. (Other email clients obviously "do" HTML emails - it's just that none of them are known to work with the mechanism Word uses to send HTML emails).
Assuming that you are using Outlook, the second problem is that the email merge process is just emailing the text that has been placed in the Data column in the EmailDataSource.doc, which is the data source for the merge to email. The way that the EmailMergeTableMaker routine works at present, that data will be a tab-separated block of text. Word will probably expand the tabs into some white space, but it will not generate an HTML table. So that is probably the origin of the alignment problem. If so, you need to ensure that that each cell contains a table instead.
It would probably be better to do that by rethinking the way that EmailMergeTableMaker works. The following "quick fix" worked on some sample data here, but I did not test situations where for example the cell is empty.
After this code...
With .Tables(1)
.Rows.Add BeforeRow:=.Rows(1)
.Cell(1, 1).Range.Text = "Recipient"
.Cell(1, 2).Range.Text = "Data"
End With
.Paragraphs(1).Range.Delete
Call TableJoiner
...insert the following:
' you should really move this Dim statement to the top
' of the Sub and merge it with the existing Dim
Dim oCellRng as Range
With .Tables(1)
For i = 2 To .Rows.Count
Set oCellRng = .Cell(i, 2).Range
oCellRng.MoveEnd wdCharacter, -1
oCellRng.ConvertToTable vbTab
Set oCellRng = Nothing
Next
End With
If you are not using Outlook, then you will not be able to use MailMerge directly to create HTML format message, and you obviously won't be able to use the Outlook object model to do it, so I think you then have to think in terms of generating HTML format emails and sending them some other way (e.g. directly via SMTP), but that is a whole other story.
The other way to send emails via Outlook is to automate Outlook, as Thomas Inzina suggests. However, that will also require you to make other changes to the way your merge works.
FWIW the routines you are using come from a tutotial by "macropod" - I don't have a link for it but a search for "macropod Catalogue MailMerge Tutorial" may lead you to it and to other ways to solve this type of problem.

Related

Adding existing Header and Footer for multiple word documents

I have around 1000 word documents in one folder which the header and footer needs to be added/changed (header need to added/changed just for the first page).
I found a very helpful VBA script which is work but I tried but can not style and format to my needs, which is shown in the attached pictures
Header Style I need
Footer Style I need
The found working code which i found in stackoverflow:
Sub openAllfilesInALocation()
Dim Doc
Dim i As Integer
Dim docToOpen As FileDialog
Set docToOpen = Application.FileDialog(msoFileDialogFilePicker)
docToOpen.Show
For i = 1 To docToOpen.SelectedItems.Count
'Open each document
Set Doc = Documents.Open(FileName:=docToOpen.SelectedItems(i))
With ActiveDocument.Sections(1)
.Headers(wdHeaderFooterPrimary).Range.Text = "Header goes here"
.Footers(wdHeaderFooterPrimary).Range.Text = "Footer goes here"
End With
Doc.Save
Doc.Close
Next i
End Sub
Thanks in advance for everybody reading and/or helping me with this question, because if I can not work it out, I need to add for around 1000 word docs headers and footers manually...... :( so thanks for helping or just trying!
Before you write code for this you need to break the task down into steps.
Open one of the documents that you need to apply the changes to.
Record a macro whilst you edit the Header style so that it has the correct formatting
Record a macro whilst you edit the Footer style so that it has the correct formatting
Edit the header of the document to include whatever logo and text you require.
Select the content of the header and save as as a Building Block - on the Header & Footer tab click "Header" then "Save Selection to Header Gallery". Ensure that you pay attention to which template you are saving it to as you will need to know this later.
Edit the footer of the document to include whatever text you require.
Select the content of the footer and save as as a Building Block - on the Header & Footer tab click "Footer" then "Save Selection to Footer Gallery". Again ensure that you pay attention to which template you are saving it to.
Now you can write your code. For example:
Sub openAllfilesInALocation()
Dim Doc As Document
Dim i As Integer
Dim BBlockSource As Template
Set BBlockSource = Application.Templates("<Full path to template you stored building blocks in>")
Dim docToOpen As FileDialog
Set docToOpen = Application.FileDialog(msoFileDialogFilePicker)
docToOpen.Show
For i = 1 To docToOpen.SelectedItems.Count
'Open each document
Set Doc = Documents.Open(FileName:=docToOpen.SelectedItems(i))
MacroToModifyHeaderStyle 'name of the macros you recorded in steps 2 & 3
MacroToModifyFooterStyle
With ActiveDocument.Sections(1)
BBlockSource.BuildingBlockEntries("Name of Header Building Block").Insert .Headers(wdHeaderFooterFirstPage).Range
BBlockSource.BuildingBlockEntries("Name of Footer Building Block").Insert .Footers(wdHeaderFooterFirstPage).Range
'you may need the following if an extra paragraph is created when adding the building block
'.Headers(wdHeaderFooterFirstPage).Range.Paragraphs.Last.Range.Delete
'.Footers(wdHeaderFooterFirstPage).Range.Paragraphs.Last.Range.Delete
End With
Doc.Save
Doc.Close
Next i
End Sub
Obviously you test your code on a copy of some of the files before attempting to run it on all of them.
Simply add the following macro to a document containing your new header & footer, then run the macro, which includes a folder browser so you can select the folder to process.
Sub UpdateDocumentHeaders()
Application.ScreenUpdating = False
Dim strFolder As String, strFile As String
Dim wdDocTgt As Document, wdDocSrc As Document
Dim Sctn As Section, HdFt As HeaderFooter
strFolder = GetFolder
If strFolder = "" Then Exit Sub
Set wdDocSrc = ActiveDocument
strFile = Dir(strFolder & "\*.doc", vbNormal)
While strFile <> ""
If strFolder & "\" & strFile <> wdDocSrc.FullName Then
Set wdDocTgt = Documents.Open(FileName:=strFolder & "\" & strFile, _
AddToRecentFiles:=False, Visible:=False)
With wdDocTgt
For Each Sctn In .Sections
'For Headers
For Each HdFt In Sctn.Headers
With HdFt
If .Exists Then
If .LinkToPrevious = False Then
.Range.FormattedText = _
wdDocSrc.Sections.First.Headers(wdHeaderFooterPrimary).Range.FormattedText
End If
End If
End With
Next
'For footers
For Each HdFt In Sctn.Footers
With HdFt
If .Exists Then
If .LinkToPrevious = False Then
.Range.FormattedText = _
wdDocSrc.Sections.First.Footers(wdHeaderFooterPrimary).Range.FormattedText
End If
End If
End With
Next
Next
.Close SaveChanges:=True
End With
End If
strFile = Dir()
Wend
Set wdDocSrc = Nothing: Set wdDocTgt = Nothing
Application.ScreenUpdating = True
End Sub
Function GetFolder() As String
Dim oFolder As Object
GetFolder = ""
Set oFolder = CreateObject("Shell.Application").BrowseForFolder(0, "Choose a folder", 0)
If (Not oFolder Is Nothing) Then GetFolder = oFolder.Items.Item.Path
Set oFolder = Nothing
End Function
As coded, the macro assumes the document you're running the macro from has only one Section, with up to three populated headers (as allowed by Word), and that all headers in the target document are to be updated to match the source document's primary header & footer. If you only want to update headers in the first Section, delete the footer loop and delete 'For Each Sctn In .Sections' and it's 'Next' later in the code and change 'For Each HdFt In Sctn.Headers' to 'For Each HdFt In .Sections(1).Headers'.

Keep Picture Ratio in Word File

I add a picture via VBA in the Left Header Cell of a Word document - works fine with the following code. Now I want to keep the ratio of the Picture but want to change the size and I don't know how to do it:
Sub AutoOpen()
Dim dd1 As Document: Set dd1 = ActiveDocument
Dim rng1 As Range, seC As Section, an(2) As Long
Dim rngO As Range, rngAN As Range
Dim strToPict As String
For Each rngO In dd1.StoryRanges
ActiveDocument.ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
If rngO.StoryType = wdEvenPagesHeaderStory Then
Set rng1 = rngO.Duplicate
For Each seC In rng1.Sections
an(0) = seC.Headers(1).Range.InlineShapes(1).Height
an(1) = seC.Headers(1).Range.InlineShapes(1).Width
Set rngAN = seC.Headers(1).Range.InlineShapes(1).Range.Duplicate
seC.Headers(1).Range.InlineShapes(1).Delete
seC.Headers(1).Range.InlineShapes.AddPicture FileName:=strToPict, _
LinkToFile:=False, SaveWithDocument:=True, Range:=rngAN
With seC.Headers(1).Range.InlineShapes(1)
.Height = 50
.LockAspectRatio = True
End With
Next
Dim i As Long
ActiveDocument.Save
'Footer changing'
For i = 1 To ActiveDocument.Sections.Count
With ActiveDocument.Sections(i)
.Footers(wdHeaderFooterPrimary).Range.Text = ActiveDocument.Name + "Text"
End With
Next
End If
Next
End Sub
EDIT: I post the whole code of the Makro.
«I add a picture via VBA in the Left Header Cell of a Word document». There is no such thing as a 'Left Header Cell' in a Word document. The only headers (and footers) Word has are Primary, First Page and Even Pages.
And, as Timothy said, you "really need to learn to use the tools at you fingertips". Moreover, having found LockAspectRatio, a simple web search - if that was really necessary - would show you how to use it.
In any event, since all it seems you're trying to do is to resize the inlineshape and repeat the primary page header, you could use something along the lines of:
Sub AutoOpen()
Application.ScreenUpdating = False
Dim Rng As Range, iShp As InlineShape, Sctn As Section, StrNm As String
With Dialogs(wdDialogInsertPicture)
.Display
StrNm = .Name
End With
With ActiveDocument
If StrNm <> "" Then
Set Rng = .Sections.First.Headers(wdHeaderFooterPrimary).Range.Tables(1).Cell(1, 1).Range
Set iShp = .InlineShapes.AddPicture(FileName:=StrNm, _
LinkToFile:=False, SaveWithDocument:=True, Range:=Rng)
With iShp
.LockAspectRatio = True
.Height = 50
End With
End If
Set Rng = .Sections.First.Footers(wdHeaderFooterPrimary).Range
.Fields.Add Range:=Rng, Type:=wdFieldEmpty, Text:="FILENAME", PreserveFormatting:=False
Rng.InsertAfter vbTab & "Text"
For Each Sctn In .Sections
Sctn.Headers(wdHeaderFooterPrimary).LinkToPrevious = True
Sctn.Footers(wdHeaderFooterPrimary).LinkToPrevious = True
Next
End With
Application.ScreenUpdating = True
End Sub
You really need to learn to use the tools at you fingertips - IntelliSense, the Object Browser, and online help.
Scrolling through the options that IntelliSense gives you, or looking up InlineShape in the Object Browser, you would find LockAspectRatio. If you weren’t sure whether that was what you needed, pressing F1 would take you to the online help.

VBA Code to change word footer in multiple files based on page number

I have a macro that runs to make a single page doc into a 5 page doc (NCR Duplicates) for all files in a folder.
I am using a set of nested IF fields in my footer, which changes the footer based on page number. The field looks like this
Text here {If{PAGE}="1""Original"{If{PAGE}="2""Copy 1"
{If{PAGE}="3""Copy 2"{If{PAGE}="4""Copy 3"{If{PAGE}="5""Copy 4"}}}}}
Other Text
I am trying to figure out how to add this footer to all the documents in a folder. It doesn't need to use field, if there is a way simply based on page number.
I have bashed my head against the wall, searched like crazy, and now come hat in hand.
The macro to make the duplicate copies is:
Sub Make5CopiesNCR()
vDirectory = BrowseForFolder
vFile = Dir(vDirectory & "\" & "*.*")
Do While vFile <> ""
Documents.Open FileName:=vDirectory & "\" & vFile
MakeCopies
vFile = Dir
Loop
End Sub
End Sub
Private Sub MakeCopies()
Dim i As Integer
Selection.WholeStory
Selection.Copy
For i = 1 To 6
Selection.PasteAndFormat wdFormatOriginalFormatting
Next
With ActiveDocument
.GoTo What:=wdGoToPage, Which:=wdGoToAbsolute, Name:=6 'Page number
.Bookmarks("\Page").Select
With Selection
.Delete
ActiveDocument.Close SaveChanges:=wdSaveChanges, OriginalFormat:=wdWordDocument
End With
End With
End Sub
The problem with using a mailmerge with your field construction is that it gets converted to the result. Try a field coded as:
{={PAGE}-1 \# "'Copy {={PAGE}-1}';;'Original'"}
Now, if you create the required 5 pages in your mailmerge main document, all the outputs will likewise be in multiples of 5 pages, with the correct page numbering.
Even if you use a mailmerge main document with only a single page, the outputs will have the field coding required to produce the correct numbering for however many more pages you want to add to the outputs.
As for replicating this in your existing files, simply create a document with the required footer content, then use a macro like:
Sub ReplicateFooter()
Application.ScreenUpdating = False
Dim DocSrc As Document, DocTgt As Document, Rng As Range
Dim StrPth As String, StrNm As String, StrSrc As String
Set DocSrc = ActiveDocument
Set Rng = DocSrc.Sections.First.Footers(wdHeaderFooterPrimary).Range
StrPth = DocSrc.Path & "\": StrSrc = DocSrc.FullName
StrNm = Dir(StrPth & "*.doc", vbNormal)
While StrNm <> ""
If StrPth & StrNm <> StrSrc Then
Set DocTgt = Documents.Open(FileName:=StrPth & StrNm, AddToRecentFiles:=False, Visible:=False)
With DocTgt
With .Sections.First.Footers(wdHeaderFooterPrimary).Range
.FormattedText = Rng.FormattedText
.Characters.Last.Text = vbNullString
End With
.Close True
End With
End If
StrNm = Dir()
Wend
Set Rng = Nothing: Set DocTgt = Nothing: Set DocSrc = Nothing
Application.ScreenUpdating = True
End Sub

Replace checkbox in MailEnvelop with custom value

I need to send an document as email body. The document contains several { FORMCHECKBOX } fields, which are either selected or not. Now, whenever I actually send the email, the checkboxes are not included at all. Because of this issue, I thougth about replacing the checkboxes with "Yes" or "No", depending on the selection.
I found this code, to replace all checkboxes, but it doesn't care for the actual selection:
Sub ChkBxClr()
Dim i As Long, Rng As Range
With ActiveDocument
For i = .FormFields.Count To 1 Step -1
With .FormFields(i)
If .Type = wdFieldFormCheckBox Then
Set Rng = .Range
.Delete
Rng.Text = "[__]"
End If
End With
Next
Set Rng = Nothing
End With
End Sub
Also this code changes the whole document and not just the email body, which I would like to prevent.
The overall code:
Sub SendEmail()
Dim Subject As String
Dim From As String
On Error Resume Next
Selection.GoTo What:=wdGoToBookmark, Name:="From"
From = Selection.Text
Subject = "Title"
Call ChkBxClr
ActiveWindow.EnvelopeVisible = True
With ActiveDocument.MailEnvelope.Item
.To = "<some mail address>"
.Subject = Subject
.Send
End With
End Sub
Sub ChkBxClr()
Dim i As Long, Rng As Range
With ActiveDocument
For i = .FormFields.Count To 1 Step -1
With .FormFields(i)
If .Type = wdFieldFormCheckBox Then
Set Rng = .Range
.Delete
Rng.Text = "[__]"
End If
End With
Next
Set Rng = Nothing
End With
End Sub
So the question is, how can I either display the checkboxes within the email body or replace the checkboxes (preferably only in message body) with a text, depending on the selection?

How can I add a table in each iteration of my loop?

I am generating a word document from excel using VBA. I have a for loop that I would like to add a [1 row, 1 column, bordered] table to. This is an area where the user can put their comments inside a word document. When I try to add .table.add I have been getting different errors ranging from object errors. This is what I have so far:
Sub GenDocumentables()
Worksheets("checklist").Activate
Dim wdApp As Word.Application
Set wdApp = New Word.Application
Dim saveName As String
Dim NumberOfCells As Integer
With wdApp
.Visible = True
.Activate
'Debug.Print .Version
.Documents.Add
With .Selection
.InsertBreak Type:=wdPageBreak
.ParagraphFormat.Alignment = wdAlignParagraphCenter
.BoldRun
.Font.Size = 13
.TypeText "Documentable Items for "
.TypeText Range("d4").Value
.BoldRun
.TypeParagraph
End With
NumberOfCells = Range("a4", Range("a3").End(xlDown)).Count
For loopcounter = 1 To 2 ' NumberOfCells
With .Selection
.Font.Bold = False
.Style = wdStyleHeading3
.TypeText Range("a3").Offset(loopcounter, 0).Value & " - "
.TypeText Range("a3").Offset(loopcounter, 4).Value
.TypeParagraph
.Font.Size = 10
.TypeText Range("a3").Offset(loopcounter, 5).Value
.TypeParagraph
.Font.Italic = True
.TypeText "<<Please enter your commentary here. Ensure all aspects of the check content are met>>"
.TypeParagraph
'-------------------ADD TABLE HERE-------------------
End With
Next
Set myRange = ActiveDocument.Range(0, 0)
ActiveDocument.TablesOfContents.Add Range:=myRange, UseFields:=False, UseHeadingStyles:=True, LowerHeadingLevel:=3, UpperHeadingLevel:=1
With .Selection
.GoTo What:=wdGoToSection, Which:=wdGoToFirst
.InsertBreak Type:=wdPageBreak
End With
saveName = Environ("UserProfile") & "\Desktop\My Word Doc_" & Format(Now, "yyyy-mm-dd hh-mm-ss") & ".docx"
.ActiveDocument.SaveAs2 saveName
'.ActiveDocument.Close
'.Quit
End With
MsgBox "done!"
End Sub
You are getting object errors due to the fact that Selection does not have a method .Table
To get around this, you'll want to use the line:
Set newTable = wdApp.ActiveDocument.Tables.Add(SomeRange,1,1)
Tables is a member of Document, which you can retrieve using the section wdApp.ActiveDocument.
Here, SomeRange needs to be defined for this to work properly.
To try and get this code running, lets try and add a few variables to make this easier.
Back where you declare your other variables add these:
Dim myRange As Word.Range
Dim wdDoc As Word.Document
Dim newTable As Word.Table
and before you enter your loop, AFTER you create the document add:
Set wdDoc = wdApp.ActiveDocument
Next, inside of your loop, but after your End With (.Selection) you could add:
Set myRange = wdDoc.Range(wdDoc.Content.End - 1, wdDoc.Content.End)
Set newTable = wdDoc.Tables.Add(myRange, 1, 1)
newTable.Cell(1, 1).Range.Text = "Hello"
Set myRange = wdDoc.Range(wdDoc.Content.End - 1, wdDoc.Content.End)
myRange.Select
Let's go through what this does.
First, it sets out custom variable myRange to be the last character in the document. This allows us to place the Table underneath everything that's already been created,
Next, it creates a table at this location, with the size 1x1.
The value of the first cell in this table is set to "Hello"
The next line then AGAIN sets the range to the bottom of the file, then selects it. This is neccessary because creating a table changes the selection to be inside of the new table. Skipping this line would have you then run the next iteration of the loop INSIDE of the table.
Hope this helps.