I'm trying to select an option from a content control dropdown in a pre built MS Word template based on values in excel. I've done this by matching the named ranges to a respective tag in the template. I was able to get all of the rich text, regular text, and checkboxes to successfully input their respective values, but when it gets to the dropdown list, it skips right past it. I've tried using the same code as the text content controls in order to proceed with the dropdown selection with no such luck. The .Tag matches the named range for all the dropdowns... I'm stumped. Here is the code. Again, all of the code works, except for the first ElseIf.
Sub Transfer()
Dim cs As Workbook
Dim wrd As Word.Application
Dim pc As Word.Document
Dim CC As ContentControl
Dim CCTag As String
Dim CStxt As String
Set cs = ThisWorkbook
Set wrd = CreateObject("Word.Application")
Set pc = wrd.Documents.Open("Template Source")
For Each CC In pc.ContentControls
On Error Resume Next
CCTag = CC.Tag
If CCTag <> "" Then
CStxt = Range(CCTag)
If CC.Type = wdContentControlRichText Or CC.Type = wdContentControlText Then
CC.Range.Text = CStxt
ElseIf CC.Type = wdContentControlComboBox Or CC.Type = wdContentControlDropdownList Then
CC.SetPlaceholderText , , CStxt
ElseIf CC.Type = wdContentControlCheckBox Then
CC.Checked = False
If CStxt = "True" Then
CC.Checked = True
ElseIf CStxt = "False" Then
CC.Checked = False
Else
CC.Checked = False
End If
End If
End If
Next CC
End Sub
Your code works for me - assuming each Excel range refers to a single cell. Your code could, however, be both improved and simplified:
Sub Transfer()
Dim wdApp As Word.Application, wdDoc As Word.Document, wdCC As Word.ContentControl
Dim CStxt As String
Set wdApp = CreateObject("Word.Application")
Set wdDoc = wdApp.Documents.Open("Template Source")
For Each wdCC In wdDoc.ContentControls
With wdCC
If .Tag <> "" Then
CStxt = ThisWorkbook.Names(.Tag).RefersToRange.Text
Select Case .Type
Case wdContentControlRichText, wdContentControlText
.Range.Text = CStxt
Case wdContentControlDropdownList, wdContentControlComboBox
.Type = wdContentControlText
.Range.Text = CStxt
.Type = wdContentControlDropdownList
'.SetPlaceholderText Text:=CStxt
Case wdContentControlCheckBox
If CStxt = "True" Then
.Checked = True
Else
.Checked = False
End If
End Select
End If
End With
Next
End Sub
I do have to wonder, though, why you'd be modifying the placeholder text rather than selecting a combobox/dropdown item corresponding to CStxt. See code modification for a simple way to update the combobox/dropdown. For comboboxes, even that isn't needed - you could simply move ', wdContentControlComboBox' to follow 'wdContentControlText'.
Firstly, I recommend that you refer to your range directly by referencing the workbook/worksheet you have declared in your variable cs This will ensure your subroutine executes on the correct workbook and correct worksheet.
If I have understood your question correctly - you would like to transfer a set of values in an Excel range over to the matching MS Word content control box - the issue would appear to be your use of
CStxt = Range(CCTag)
You cannot assign a variable of type string (CStxt) to an entire range of cells. This would generate a "Runtime error 13: Type mismatch" when executing your code.
In order to get this line to work, I had to change it to a single value/cell reference. See example below;
CStxt = cs.Sheets(1).Range("A1")
In this example, cell A1 has the value you want to transfer over to the relevant MS Word content control. This has the effect of giving the content control a display value equal to the Excel value, but is not added to the list to allow you to select it.
You can change the reference to A1 so that your named range is a single cell.
Related
I have a Word file with approximately 10 pages and 20 tables. Some of these tables have checkboxes. I want to copy these tables into an Excel file.
The following code copies all tables from my Word file into my Excel file:
Sub Import()
Option Explicit
Dim wb As Workbook
Dim sh As Worksheet
Dim sheet As Worksheet
Dim lzeile As Integer
Set wb = ActiveWorkbook
Set sh = wb.ActiveSheet
Set sheet = wb.Worksheets("Tabelle1")
Dim Btxt As Object
Set Btxt = CreateObject("Word.Application")
Btxt.Visible = True
Btxt.documents.Open "C:\Users\*.doc" '*=path
lzeile = 0
For i = 1 To 20
Btxt.ActiveDocument.Tables(i).Range.Copy
Application.Goto sheet.Cells(1 + lzeile, 1)
sheet.PasteSpecial Format:="HTML", Link:=False, DisplayAsIcon:=False
lzeile = sheet.Cells(Rows.Count, 1).End(xlUp).Row
lzeile = lzeile + 1
sheet.Cells(lzeile, 1) = "Tabelle" & i
Next i
Btxt.Quit
Set Btxt = Nothing
Set wb = Nothing
Set sh = Nothing
End Sub
It does not copy checkboxes or the value (0 = not checked / 1 = checked) of the checkbox.
I can write the value of a checkbox into a cell in my excel sheet with this line:
sheet.Cells(j, 10) = Btxt.ActiveDocument.Tables(i).FormFields.Item("Kontrollkästchen" & j).Result
With a loop j over all "Kontrollkästchen" (german translation of contentcontrol or formfield item) so basically the name of all formfield items in this Word file.
How can I get the position of these formfield items or identify which formfield item / ContentControl is in which table?
I tried to go through all rows and columns in each table because none of them are larger than 10x10. But I can´t find a way to check if a checkbox is maybe in table 3 on column 5 row 5 and then read the name of this checkbox to a safe the value (0 / 1) in the Excel cell on the same position in my copied table.
The solution depends on whether they're formfields or content controls.
Assuming they're formfields:
Sub Demo()
Dim i As Long, j As Long, Rng As Range
With ActiveDocument
For i = .FormFields.Count To 1 Step -1
With .FormFields(i)
If .Type = wdFieldFormCheckBox Then
j = Abs(.CheckBox.Value)
Set Rng = .Range
.Delete
Rng.Text = j
End If
End With
Next
End With
End Sub
Assuming they're content controls:
Sub Demo()
Dim i As Long, j As Long, Rng As Range
With ActiveDocument
For i = .ContentControls.Count To 1 Step -1
With .ContentControls(i)
If .Type = wdContentControlCheckBox Then
j = Abs(.Checked)
Set Rng = .Range
.Delete
Rng.Text = j
End If
End With
Next
End With
End Sub
For the sake of simplicity and clarity, the sample code below leaves out the parts having to do with Excel, as well as creating the instance of the Word Application. It shows only how to access the Word document's checkboxes and convert those to static values.
At the end, also, the document is closed without saving changes, which means forms protection and the checkboxes should be left intact - the macro will not have affected them.
Note: You should have Option Explicit at the top of the code page, not inside a "Sub".
How it works
The document to be processed is opened and at that moment set to an object (doc). Use this instead of ActiveDocument as it will be much clearer and, in case the user would try to do something, won't affect macro execution.
If the document has forms protection, this must be turned off in order to delete the checkboxes and insert static values.
Then all the form fields are looped. If they are checkboxes, the value is determined, the checkbox removed and the value assigned to the range the checkbox occupied.
After this has completed comes the code to transfer data to Excel. Then the document is closed without saving changes.
Sub ConvertCheckBoxesToValues()
Dim ff As Object ' Word.FormField
Dim doc As Object ' Word.Document
Dim cbValue As String
Dim rngFF As Object ' Word.Range
Set doc = Btxt.Documents.Open("C:\Users\*.doc") '*=path
If doc.ProtectionType <> -1 Then 'wdNoProtection
doc.Unprotect
End If
For Each ff In doc.FormFields
If ff.Type = 71 Then 'wdFieldFormCheckBox
If ff.CheckBox.value = True Then
cbValue = "1"
Else
cbValue = "0"
End If
Set rngFF = ff.Range
ff.Delete
rngFF = cbValue
End If
Next
'Transfer the information to Excel, then
doc.Close 0 'wdDoNotSaveChanges
End Sub
I'm building a code that will use a template (a Word doc embedded in Excel) and will find and replace certain words in the template using the inputs from Excel. I have successfully coded the opening of the template, find and replace in the template.
But after that, when I check the embedded Word doc in Excel, the replaced words were saved. I don't want to override the contents of the template but every time I run my code, it automatically saves the changes made during the find and replace. I just want it to find and replace, then save a copy to my local folder.
I'm using late binding as there is a limitation in the version of Excel that our team is using.
I don't know if the function of the below code is the one causing the changes to be saved in the embedded Word doc.
.Execute Replace:=2 'wdReplaceAll
Here is the my full code:
Sub Button1_Click()
Application.ScreenUpdating = False
Set WDApp = CreateObject("Word.Application")
WDApp.Visible = True
Set WDDoc = Sheets("Sheet1").OLEObjects("Template_112225")
WDDoc.Verb Verb:=xlOpen
WDApp.Selection.WholeStory
Call SplitCell
Call Find("<Part Num>", Sheets("Sheet2").Cells(8, 4).Value)
Call Find("<Dataset>", Sheets("Sheet2").Cells(7, 3).Value)
Call Find("<Letter>", Sheets("Sheet2").Cells(8, 5).Value)
Set WDDoc = Nothing
Set WDApp = Nothing
Set Rng = Nothing
End Sub
Sub Find(Find_Value As String, New_Value As String)
With WDApp.Selection.Find
.Text = Find_Value
.Replacement.Text = New_Value
.Forward = True
.Wrap = 1 'wdFindContinue
.Execute Replace:=2 'wdReplaceAll
End With
End Sub
Sub SplitCell()
Dim txt As String
Dim i As Integer
Dim NumberLetter As Variant
txt = Sheets("Sheet2").Cells(8, 3).Value
NumberLetter = Split(txt, "/")
For i = 0 To UBound(NumberLetter)
Cells(8, i + 4).Value = NumberLetter(i)
Next i
End Sub
Also is it possible to have a code that will make the Save As dialog box appear? So the user can have a choice on where to save the modified copy.
Since nobody was able to help me with the problem I posted here before (link is below), I am now trying to solve the task through VBA.
Finding a heading in word file and copying entire paragraph thereafter to new word file with python
To briefly recap, I have a large amount of word files, which I would like to reduce to a more readable size each. In each file, there is one heading that appears several times, always formated as a 'Heading 2'. I look for this specific heading which occurs several times in a document and I want to copy all the text parts in just these chapters with the respective heading to a new word document.
I decided to create an excel file in which I list the files and the respective heading of the chapters that I want to copy (see picture below).
To do this now I have written the following code:
Sub SelectData()
Application.ScreenUpdating = False
Dim WdApp As Word.Application
Set WdApp = CreateObject("Word.Application")
Dim Doc As Word.Document
Dim NewDoc As Word.Document
Dim HeadingToFind As String
Dim ChapterToFind As String
Dim StartRange As Long
Dim EndRange As Long
Dim WkSht As Worksheet
Dim LRow As Long
Dim i As Long
Set WkSht = ThisWorkbook.Sheets("Sheet1")
LRow = WkSht.UsedRange.Cells.SpecialCells(xlCellTypeLastCell).Row
With WkSht
For i = 1 To LRow
If Dir(.Cells(i, 1).Text, vbNormal) = "" Then
.Cells(i, 3).Value = "Please check File Location"
Else
Set Doc = WdApp.Documents.Open(Filename:=.Cells(i, 1).Text, _
AddToRecentFiles:=False, Visible:=False, ReadOnly:=False)
Set NewDoc = Documents.Add
ChapterToFind = LCase(.Cells(i, 2).Text)
With Doc
Selection.HomeKey Unit:=wdStory
With Selection
With .Find
.ClearFormatting
.Text = ChapterToFind
.MatchWildcards = False
.MatchCase = True
.Execute
End With
If .Find.Found Then
.Collapse wdCollapseStart
With .Find
.Text = ""
.Style = "Heading 2"
.Forward = False
.Execute
End With
.MoveDown Count:=1
.HomeKey Unit:=wdLine
StartRange = .Start
.Find.Forward = True
.Find.Execute
.Collapse wdCollapseStart
.MoveUp Count:=1
.EndKey Unit:=wdLine
EndRange = .End
Doc.Range(StartRange, EndRange).Copy
NewDoc.Content.Paste
NewDoc.SaveAs2 Doc.Path & "Clean" & ".docx", wdFormatFlatXML
Else
WkSht.Cells(i, 4).Value = "Error Chapter Not Found"
End If
End With
End With
WdApp.Quit
Set Doc = Nothing: Set NewDoc = Nothing: Set WdApp = Nothing: Set WkSht = Nothing
Application.ScreenUpdating = True
End If
Next
End With
End Sub
However I am really struggling. It seems to not work as I constantly get an error with the command (RunTimeError 438):
Selection.HomeKey Unit:=wdStory
I am aware that I have to activate the Microsoft Word 15.0 Object Library in the references to be able to get word commands. Nevertheless it is not working.
I would greatly appreciate any help, I am also open to other suggestions of course.
The word files look something like in the picture below, however the chapter that I want to extract can occur several times within one word document. As a result my code would probably need a loop or something, I was not able to get this done.
Also I have considered the following links to the topic:
Use VBA with Powerpoint to Search titles in a Word Doc and Copy Text into another Word Document
VBA: open word from excel
word vba: select text between headings
Did I understand this correctly? The following code is the core of what I think you're trying to do. It finds the first Heading 2, then finds all the paragraphs after it until it finds another header of any type or the end of the document. startCopyRange and endCopyRange is the range of those paragraphs. You'll have to piece this into your Excel routine.
A few notes. Always save the active document to a variable and work from that; the user is then free to change active documents while this routine is running. Never use Selection, always use ranges. Never use relative movements like Move, always use API calls.
Sub SelectData()
Dim Doc As Word.Document
Set Doc = ActiveDocument
Dim findRange As Range
Set findRange = Doc.Range
ChapterToFind = "My Chapter"
findRange.Find.Text = ChapterToFind
findRange.Find.Style = "Heading 2"
findRange.Find.MatchCase = True
Dim startCopyRange As Long
Dim endCopyRange As Long
Do While findRange.Find.Execute() = True
startCopyRange = findRange.End + 1
endCopyRange = -1
'findRange.Select
Dim myParagraph As Paragraph
Set myParagraph = findRange.Paragraphs(1).Next
Do While Not myParagraph Is Nothing
myParagraph.Range.Select 'Debug only
If InStr(myParagraph.Style, "Heading") > 0 Then
endCopyRange = myParagraph.Range.Start - 0
End If
If myParagraph.Next Is Nothing Then
endCopyRange = myParagraph.Range.End - 0
End If
If endCopyRange <> -1 Then
Doc.Range(startCopyRange, endCopyRange).Select 'Debug only
DoEvents
Exit Do
End If
Set myParagraph = myParagraph.Next
DoEvents
Loop
Loop
End Sub
Good Morning!
I am creating a tool to format many word documents with the same settings, and am working on setting all document body data to the column count specified, and the margins as specified. When run, this code works, but does not set the left/ right margins appropriately. The code should set each of them to the same value.
When run it seems to be variable. For instance, if I choose 0.3; the left margin will end of as 0.2 and the right will be 0.4. To make it a little weirder, if I manually go into the custom margin settings in MS Word, it states it is at the appropriate numbers (0.3) even when the margin bar on the page is not set there.
Has anyone worked with margins via vb to know if this is a setting issue, or if there is a more accurate way? I think it may have something to do with the column spacing .SpaceAfter = InchesToPoints(frmWordEdit.txtColumnSpacing), but I am unsure.
Any help is appreciated!
Sub AddRemoveWatermark(strReplaceText As String)
'Word Variables
Dim wrdApplication As Word.Application
Dim wrdDocument As Word.Document
Dim wrdSection As Word.section
Dim wrdSelection As Word.Selection
Dim wrdHeader As Word.HeaderFooter
Dim rngHeader As Word.Range
Dim rngFooter As Word.HeaderFooter
Dim spShape As Word.Shape
Dim strDocumentName As String
Dim strPath As String
Dim strBBPath As String
Dim lngCount As Long
' Open the file dialog
With Application.FileDialog(msoFileDialogOpen)
.AllowMultiSelect = True
.Show
Set wrdApplication = New Word.Application
' Display paths of each file selected
For lngCount = 1 To .SelectedItems.Count
strPath = .SelectedItems(lngCount)
Set wrdDocument = wrdApplication.Documents.Open(strPath)
strDocumentName = wrdDocument.FullName 'Record the document name
wrdApplication.Templates.LoadBuildingBlocks
wrdApplication.Visible = True
'Document Layout
If frmWordEdit.chkDocumentLayout.Value = True Then
'Change Columns
If frmWordEdit.chkColumns = True Then
With wrdDocument.PageSetup.TextColumns
.SetCount NumColumns:=frmWordEdit.txtColumns
'.Add EvenlySpaced:=True
'.Width = InchesToPoints(3)
'.SpaceAfter = InchesToPoints(0.3)
End With
Dim i As Integer
If frmWordEdit.txtColumns > 1 Then
For i = 1 To frmWordEdit.txtColumns - 1
With wrdDocument.PageSetup.TextColumns(i)
'.Width = InchesToPoints(4)
.SpaceAfter = InchesToPoints(frmWordEdit.txtColumnSpacing)
End With
Next
End If
End If
'Change Margins
If frmWordEdit.chkMargins = True Then
With wrdDocument.PageSetup
.LeftMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMLeftRight)
.RightMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMLeftRight)
.TopMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMTop)
.BottomMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMBottom)
End With
End If
End If
'Document Design
If frmWordEdit.chkDocumentDesign.Value = True Then
If frmWordEdit.chkMHeader = True Then
With wrdDocument.PageSetup
.HeaderDistance = wrdApplication.InchesToPoints(frmWordEdit.txtMHeader)
End With
End If
If frmWordEdit.chkMFooter = True Then
With wrdDocument.PageSetup
.FooterDistance = wrdApplication.InchesToPoints(frmWordEdit.txtMFooter)
End With
End If
End If
End Sub
This is the part of your code which sets the margins.
With wrdDocument.PageSetup
.LeftMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMLeftRight)
.RightMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMLeftRight)
.TopMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMTop)
.BottomMargin = wrdApplication.InchesToPoints(frmWordEdit.txtMBottom)
End With
I find no fault with the syntax (except that you don't need to specify wrdApplication unless you run the code from another application, perhaps Excel). Since the code appears without fault errors must have their origin in the reference to frmWordEdit. I suggest that you run this code with plain numbers, expressed in points, and see if you still get the same result.
frmWordEdit.txtMLeftRight would appear to be a Textbox. Since you don't specify which property you are referring to it must be the default which is the Value property. The Value property of a Textbox holds a string which you feed into the InchesToPoints function. That function takes a single, if I'm not mistaken - anyway, a numeric value. Therefore I suspect that the string isn't correctly translated. Try something like InchesToPoints(Val(frmWordEdit.txtMLeftRight)).
Can anyone tell me how to improve this macro?
All the macro does is it just reads an Excel file for a list a accounts to update in an application (SmarTerm Beta). It technically already accomplishes the goal, but is there a way to code it so that while it’s reading the Excel file, the coordinates of the cells from which to read the account numbers and also the coordinates of the cells in which to write an output don’t depend on a "pre-selected" a cell? The risk with selecting a cell is that if someone were to accidentally select a different cell while the macro is running, everything will get screwed up.
Here's my current code:
Public oExcelObj As Object
Function WaitSystem(Optional NoDialog as Variant) As Boolean
Dim nContinue as Integer
Dim nTimeOut as Integer 'In seconds.
'The default timeout for each command is 3 minutes.
'Increase this value if your host requires more time
'for each command.
nTimeOut = 10
If IsMissing(NoDialog) then NoDialog = False
'Wait for response from host.
Session.EventWait.Timeout = nTimeOut
Session.EventWait.EventType = smlPAGERECEIVED
Session.EventWait.MaxEventCount = 1
WaitSystem = True
If Session.EventWait.Start = smlWAITTIMEOUT Then
If NoDialog Then
WaitSystem = False
Else
nContinue = QuerySyncError()
If nContinue <> ebYes then WaitSystem = False
End If
End If
Set LockStep = Nothing
End Function
'Establish link. Search for Excel.
Function OleLinkConnection
Const XlMaximized = &HFFFFEFD7
Titlebar$ = AppFind$("Microsoft Excel")
If Titlebar$ <> "" Then
bIsExcelActive = True
If AppGetState(Titlebar$) = ebMinimized Then
AppSetState 2, Titlebar$
End If
Else
bIsExcelActive = False
End If
If bIsExcelActive Then
'Create Excel Object using current instance of Excel.
Set oExcelObj = GetObject(, "Excel.Application")
Else
'Create Excel Object using a new instance of Excel.
Set oExcelObj = CreateObject("Excel.Application")
End If
Version = oExcelObj.Application.Version
oExcelObj.ScreenUpdating = True
oExcelObj.Displayalerts = True
oExcelObj.Visible = true
End Function
Sub JPBmacro
Dim AccountNumber As String
Dim Temp As Integer
Begin Dialog StartDialogTemplate ,,211,74,"Run JPBmacro?"
OKButton 60,12,92,20,.Proceed
CancelButton 60,40,92,20,.Exit
End Dialog
Dim StartDialog As StartDialogTemplate
r% = Dialog(StartDialog)
If r% = 0 Then End
g$ = "G:\DATA\outputfile.xlsx"
oleCode = OleLinkConnection
oExcelObj.Workbooks.Open g$
oExcelObj.Range("A1").Select ‘<----This selects the cell from which all coordinates are based off of. The coordinates of oExcelObj.ActiveCell.Offset(Y,X).Value VBA depend on selecting a cell.
NEXTACCOUNT:
Temp = 0
AccountNumber = oExcelObj.ActiveCell.Offset(Temp,0).Value
While AccountNumber <> ""
Session.SendKey "CLEAR"
If WaitSystem = False Then End
Session.Send "ACTU " & AccountNumber
Session.SendKey "ENTER"
If WaitSystem = False Then End
If Trim(Session.ScreenText(4,6,1,22)) = "INVALID ACCOUNT NUMBER" Or Trim(Session.ScreenText(4,6,1,19)) = "ACCOUNT NOT ON FILE" Then
oExcelObj.ActiveCell.Offset(Temp,1).Value = Trim(Session.ScreenText(4,6,1,22))
GoTo RESTARTLOOP
End If
UPDATEIOV:
If Trim(Session.ScreenText(13,76,1,1)) = "Y" Then
oExcelObj.ActiveCell.Offset(Temp,1).Value = "Account already flagged as institutional."
Else
Session.Row = 13
Session.Column = 76
Session.send "Y"
Session.SendKey "ENTER"
If WaitSystem = False Then End
oExcelObj.ActiveCell.Offset(Temp,1).Value = Trim(Session.ScreenText(24,2,1,50))
End If
RESTARTLOOP:
Temp = Temp + 1
AccountNumber = oExcelObj.ActiveCell.Offset(Temp,0).Value
Wend
ENDNOW:
oExcelObj.Workbooks.Close
MsgBox "All Done!"
End Sub
Why not keep the reference to the first cell?
Dim rng as Range
Set rng = oExcelObj.Range("A1")
i=1
...
x = rng.Cell(i,1).Value
'Or faster yet is reading all the values into an variant array.
Dim array() as Variant
array = rng.Resize(N,M).Value
' Work with array as
x = array(i,1)
Given the comment from assylias and that another poster has since "answered" with this approach:
I can't see where oExcelObj is instantiated? Or how you are referring to a specific sheet.
Regardless of which,
you can avoid select by setting a range, ie Set rng1 = oExcelObj.Sheets(1).Range("A1")
and then use offsets from rng1.
The user won't be able to interfere while the code is running