Replace checkbox in MailEnvelop with custom value - vba

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?

Related

When using vba TextColumns method to split two column a part of a word document, it will affect the entire document

I have a problem when using VBA for column operation.
I want to select an area in a Word document that contains several paragraphs, and then I want to split them from one column into two.
My VBA code is as follows:
Public Sub testSplitColumn()
Dim targetDoc As Document
Dim sourceFileName As String
sourceFileName = "file path"
Set targetDoc = Documents.Open(sourceFileName, , True)
targetDoc.Paragraphs(503).range.Select
'Splitting column on word
With targetDoc.Paragraphs(503).range.PageSetup.TextColumns
.SetCount NumColumns:=2
.EvenlySpaced = True
.LineBetween = False
End With
End Sub
It runs, but the result is wrong.
It is columnizing the paragraphs in the entire document, not just the selected paragraphs in the code.
I got a macro code that can achieve the correct effect through the method of word macro recording:
Sub split()
If ActiveWindow.View.SplitSpecial <> wdPaneNone Then
ActiveWindow.Panes(2).Close
End If
If ActiveWindow.ActivePane.View.Type <> wdPrintView Then
ActiveWindow.ActivePane.View.Type = wdPrintView
End If
With Selection.PageSetup.TextColumns
.SetCount NumColumns:=2
.EvenlySpaced = True
.LineBetween = False
End With
End Sub
But it's no different from mine.
How can I fix my VBA code?
As #JerryJeremiah said: you need section breaks before and after your selection.
When recording a macro - they will be inserted as well.
I would create a generic sub to insert the section breaks:
Public Sub test_splitTo2Columns()
'your original code
Dim targetDoc As Document
Dim sourceFileName As String
sourceFileName = "file path"
Set targetDoc = Documents.Open(sourceFileName, , True)
'calling the generic function to test with specific paragraph
splitTo2Columns targetDoc.Paragraphs(503).Range
'this will work too - splitting the selected range
splitTo2Columns ActiveDocument.Selection.Range
End Sub
Public Sub splitTo2Columns(rg As Range, Optional fSplitWholeParagraphs As Boolean = True)
Dim rgToSplit As Range
Set rgToSplit = rg.Duplicate
If fSplitWholeParagraphs = True Then
'in case rg = selection and selection is only a single character
rgToSplit.Start = rgToSplit.Paragraphs.First.Range.Start
rgToSplit.End = rgToSplit.Paragraphs.Last.Range.End
End If
insertSectionBreakContinous rgToSplit, wdCollapseStart
insertSectionBreakContinous rgToSplit, wdCollapseEnd
rgToSplit.Start = rgToSplit.Start + 1 'move behind first section break
With rg.PageSetup.TextColumns
.SetCount NumColumns:=2
.EvenlySpaced = True
.LineBetween = False
End With
End Sub
Private Sub insertSectionBreakContinous(rg As Range, startEnd As WdCollapseDirection)
Dim rgBreak As Range
Set rgBreak = rg.Duplicate
With rgBreak
.Collapse startEnd
.InsertBreak wdSectionBreakContinuous
End With
End Sub

Replacing checkboxes in MS-Word

I have a document with a large number of checkboxes spread around the text and I would like to replace all checkboxes with characters.
Example:
If checkbox is checked then replace it with "A"
If checkbox is not Checked then replace it with "O"
For the time being I can only replace all checkboxes with a letter regardless of their state (checked or unchecked). I need to improve my macro so it recognizes the state of the checkbox and replacing it with the right litteral.
Thanks in advance
Sub Checkbox_Replacement()
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 = "A"
End If
End With
Next
Set Rng = Nothing
End With
End Sub
Expected Result
If checkbox is checked then replace it with "A"
If checkbox is not Checked then replace it with "O"
Actual Result
All checkboxes are replaced with "A"
You need a second If..Else block to check the condition of the checkbox:
If .Type = wdFieldFormCheckBox Then
Set Rng = .Range
If .CheckBox.Value = True Then
Rng.Text = "A"
Else
Rng.Text = "O"
End If
.Delete
End If
The answer to the problem was to remove the .Delete
Sub Checkbox_Replacement()
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
If .CheckBox.Value = True Then
Rng.Text = "A"
Else
Rng.Text = "O"
End If
End If
End With
Next
Set Rng = Nothing
End With
End Sub

VBA to add a row to the bottom of the table and insert Rich Text Content Control

I have a form in Word 2013. In it are various tables and I want users to be able to add a row to the bottom of the table. I have done this with the following code:
`Dim oTable As table
Dim oCell As Cell
Dim oPrevRow as Row, oNewRow As Row
Dim iColumn As Long
Set oTable = ActiveDocument.tables (1)
Set oPrevRow = oTable.Rows(oTable.Rpws.Count)
oTable.Rows.Add
Set oNewRow = oTable.Rows(oTable.rows.Count)`
What I want is for all 7 cells in that new row to have Rich Text Content Control inserted into them. How do I do this?
The following code will work with a table bookmarked as 'TblBkMk' anywhere in the document body. Comments in the code show how you could test for a particular table instead. Simply add the code to the 'ThisDocument' code module of the document or its template. The macro triggers when you exit the last content control in the table. The code also provides for the document to have 'read-only' or 'filling in forms' protection (add the password to the code where indicated, if you use one)
Private Sub Document_ContentControlOnExit(ByVal CCtrl As ContentControl, Cancel As Boolean)
'The following code conditionally adds a new row, with content controls, to the designated table.
Dim i As Long, j As Long, Prot As Long
Const Pwd As String = "" 'Insert password (if any) here
'Bookmarking the table provides the flexibility of being able to deal with the addition/deletion
' of other tables before the one we want to process.
Const StrBkMk As String = "TblBkMk"
'Exit if we're not in a table - we don't really need this is using a bookmarked table,
' but it's a safeguard against the bookmark having been expanded/moved.
If CCtrl.Range.Information(wdWithInTable) = False Then Exit Sub
With ActiveDocument
If .Bookmarks.Exists(StrBkMk) = False Then
MsgBox "The table bookmark: '" & StrBkMk & "' is missing." & vbCr & _
"Please add it to the relevant table before continuing.", vbExclamation
Exit Sub
End If
End With
With CCtrl
'Check that the Content Control is within our bookmarked table's range.
If .Range.InRange(ActiveDocument.Bookmarks(StrBkMk).Range) = False Then Exit Sub
' One could test for a particular table instead, in which case all the code dealing
' with wdWithInTable & StrBkMk can be deleted. For example:
'If .Range.InRange(ActiveDocument.Tables(1).Range) = False Then Exit Sub
'Get the number of ContentControls in the table
i = .Range.Tables(1).Range.ContentControls.Count
'Get our ContentControl's index # in the table
j = ActiveDocument.Range(.Range.Tables(1).Range.Start, .Range.End).ContentControls.Count
'Check that we're using the last content control
If i <> j Then Exit Sub
End With
'Solicit user input
If MsgBox("Add new row?", vbQuestion + vbYesNo) <> vbYes Then Exit Sub
With ActiveDocument
' Un-protect the document, if applicable
Prot = .ProtectionType
If .ProtectionType <> wdNoProtection Then
Prot = .ProtectionType
.Unprotect Password:=Pwd
End If
With Selection.Tables(1).Rows
'Insert an empty paragraph after our table, then replace it with a replica of the last row
With .Last.Range
.Next.InsertBefore vbCr
.Next.FormattedText = .FormattedText
End With
'Reset all content controls in the new last row
For Each CCtrl In .Last.Range.ContentControls
With CCtrl
If .Type = wdContentControlCheckBox Then .Checked = False
If .Type = wdContentControlRichText Or .Type = wdContentControlText Then .Range.Text = ""
If .Type = wdContentControlDropdownList Then .DropdownListEntries(1).Select
If .Type = wdContentControlComboBox Then .DropdownListEntries(1).Select
If .Type = wdContentControlDate Then .Range.Text = ""
End With
Next
End With
'Update the bookmarked range
.Bookmarks.Add Name:=StrBkMk, Range:=Selection.Tables(1).Range
' Re-protect the document, if applicable
.Protect Type:=Prot, Password:=Pwd
End With
End Sub

Mailmerge MailFormat and alighnment issues

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.

In which field the cursor is? (ms word, vba)

In a VBA Word macro, I'd like to get a Field-object for the field which contains the cursor.
The obvious try fails:
Private Sub Try1()
MsgBox Selection.Fields.Count
End Sub
The array is empty. Then I tried:
Private Sub Try2()
Dim oRange As Range
Set oRange = Selection.GoTo(What:=wdGoToField)
MsgBox oRange
End Sub
The cursor does not move, the message is empty.
I can iterate over ActiveDocument.Fields, compare the ranges and find the containing fiels. But probably there is a simple direct way?
My current production code with iteration over Document.Fields:
Sub Test()
Dim oField As Field
Set oField = FindWrappingField(Selection.Range)
If oField Is Nothing Then
MsgBox "not found"
Else
MsgBox oField
End If
End Sub
Private Function FindWrappingField(vRange As Range)
Dim oField As Field
Dim nRefPos As Long
' If selection starts inside a field, it also finishes inside.
nRefPos = vRange.Start
' 1) Are the fields sorted? I don't know.
' Therefore, no breaking the loop if a field is too far.
' 2) "Code" goes before "Result", but is it forever?
For Each oField In vRange.Document.Fields
If ((oField.Result.Start <= nRefPos) Or (oField.Code.Start <= nRefPos)) And _
((nRefPos <= oField.Result.End) Or (nRefPos <= oField.Code.End)) Then
Set FindWrappingField = oField
Exit Function
End If
Next oField
Set FindWrappingField = Nothing
End Function
The following function determines whether the selection spans or is within a field.
Function WithInField(Rng As Word.Range) As Boolean
' Based on code by Don Wells: http://www.eileenslounge.com/viewtopic.php?f=30&t=6622
' Approach : This procedure is based on the observation that, irrespective of _
a field's ShowCodes state, toggling the field's ShowCodes state _
twice collapses the selection to the start of the field.
Dim lngPosStart As Long, lngPosEnd As Long, StrNot As String
WithInField = True
Rng.Select
lngPosStart = Selection.Start
lngPosEnd = Selection.End
With Selection
.Fields.ToggleShowCodes
.Fields.ToggleShowCodes
' Test whether the selection has moved; if not, it may already have been _
at the start of a field, in which case, move right and test again.
If .Start = lngPosStart Then
.MoveRight
.Fields.ToggleShowCodes
.Fields.ToggleShowCodes
If .Start = lngPosStart + 1 Then
WithInField = False
End If
End If
End With
End Function
You can use the function with code like:
Sub TestWithInField()
Dim Rng As Word.Range, c As Word.Range, StrRslt As String
Set Rng = Selection.Range
For Each c In Rng.Characters
StrRslt = StrRslt & c.Text & ",WithInField:" & WithInField(Rng:=c) & vbCr
Next
Rng.Select
MsgBox StrRslt
End Sub
I had the same problem and I solved with the code below:
Sub Test()
NumberOfFields = Selection.Fields.Count
While NumberOfFields = 0
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend
NumberOfFields = Selection.Fields.Count
Wend
End Sub
Of course, I have to know that the cursor is in a field.
Apparently, when you select a range extending to the right, at some moment the field will be selected. The end of the range doesn't count (it not acuses a field range)
I use this code
Sub GetFieldUnderCursor()
Dim NumberOfFields As Integer
Dim oFld As Field
Dim TextFeld As String
Dim Typ As Integer
Dim pos As Integer
Dim NameOfField As String
'update field. Cursor moves after the field
Selection.Fields.Update
'select the field
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend
'check if there is a field
NumberOfFields = Selection.Fields.Count
If NumberOfFields = 0 Then
MsgBox "No field under cursor"
Exit Sub
End If
Set oFld = Selection.Fields(1)
TextFeld = Trim(oFld.Code.Text)
Typ = oFld.Type '85 is DOCPROPERTY, 64 is DOCVARIABLE
If Typ = 85 Or Typ = 64 Then
pos = InStr(15, TextFeld, " ")
If pos > 0 Then
NameOfField = Trim(Mid(TextFeld, 12, pos - 11))
MsgBox NameOfField
End If
End If
End Sub