Word VBA code to fetch Drop Down list data - vba

I am trying to write a code in Word that allows me to fetch data from Content Control Drop Down lists. This data is being pulled from a previously saved Word file, that I reference at the start of script (but don't show here as that is not the problem).
I have this working for other types of Content Control (example below), but I cannot figure out how this will work for Drop Down lists.
Here is my ineffective code:
For l = 1 To 28
Windows(ReportWindowName).Activate
TagName = "Rating" & l
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
ccc = Selection.Text
OriginalDocument.Activate
TagName = "Rating" & l
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
Selection.Text = ccc
Next l
The code falls over at Selection.Text. I need to modify something to allow the code to fetch entries in Drop Down lists.
Below is another very similar code from the same command, that works, but returns data from text fields rather than Drop Down lists saved in the dame file:
For j = 1 To 6
Windows(ReportWindowName).Activate
TagName = "Mandatory" & j
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
ccc = Selection.Text
OriginalDocument.Activate
TagName = "Mandatory" & j
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
Selection.Text = ccc
Next j
Would appreciate any help modifying my loop code to fetch the Drop Down list results.
Many thanks!

If you are trying to get text from the Content Control, what you need is at most
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
' Let's just show the "display name"
Debug.Print cc.Range.Text
You could shorten that to
Set ccs = doc.SelectContentControlsByTag(TagName)
' Let's just show the "display name"
Debug.Print ccs(1).Range.Text
or even further if you like.
The reason that the code you have at the moment fails is because it's actually trying to put text into the Content Control. You can do that with a Text control but not with a Dropdown List
(Following up on your comment) If you want to set the dropdownlist to a certain value, you basically have to identify which item in the DropDownListEntries collection is the correct one, then select it. Each DropDownListEntry within a ContentControl has a unique Index, unique Text (display text) and Value (hidden value).
You can get the Text from a dropdown by looking at the .Range.Text of the source ContentControl, but you can't use that as an index into the target ContentControl's list entries, so you have to iterate:
So, if ccc contains the text you want to display, you would need something like
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
' This asumes you know this is a dropdown list cc
Dim ddle as Word.ContentControlListEntry
For Each ddle in cc.DropdownListEntries
If ddle.Text = ccc Then
ddle.Select
Exit For
End If
Next
Or, you can get the Index from the source control (and you would have to iterate the source control's listentries to do that). Let's say it's in variable idx. Then all you need is
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.DropdownListEntries(idx).Select
(In fact you can do it all in one
doc.SelectContentControlsByTag(TagName)(1).DropDownlistEntries(idx).Select
but I generally find using multiple statements makes debugging easier).
So using this approach, you either have to iterate one set of list entries or the other (or both, if you want to use the Value).
The other technique would be to map the control to an Element in a CustomXMLPart and just update the Element value. Word then propagates the value to all the ContentControls mapped to that Element. There is quite a bit to learn and it may seem like complication that you don't need, but when you get to the end I hope you will see why this is actually quite a neat approach.
At its simplest, it works like this. Let's suppose you have one DropDown Content Control in your document.
Then you can (re) create an XML Part and map the content control to it like this. You would only need to execute this piece of code once for a document. If your documents are based on templates or made from copies of other documents, that's once for the template/original.
Option Explicit
' A namespace URI can just be a piece of text, but its better if you can use
' something that you "own" such as a domain name.
' There is nothing special about this name.
Const myNameSpace As String = "myns0"
Sub recreateCXPandMapCCs()
Dim ccs As Word.ContentControls
Dim cxp As Office.CustomXMLPart
Dim i As Integer
Dim r As Word.Range
Dim s As String
' There is nothing special about these element names.
' You can use your own
s = ""
s = s & "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf
s = s & "<ccvalues1 xmlns='" & myNameSpace & "'>" & vbCrLf
s = s & " <dropdown1/>" & vbCrLf
s = s & "</ccvalues1>"
With ActiveDocument
' select and delete any existing CXPs with this namespace
For Each cxp In .CustomXMLParts.SelectByNamespace(myNameSpace)
cxp.Delete
Next
' Create a new CXP
Set cxp = .CustomXMLParts.Add(s)
' Connect your dropdown. Instead, you can do this manually in the XML Mapping
' Pane in the Developer tab
' For an XML Part that only has one namespace the prefix mapping should always be "ns0".
.ContentControls(1).XMLMapping.SetMapping "/ns0:ccvalues[1]/ns0:dropdown1[1]", , cxp
Set cxp = Nothing
End With
End Sub
Then, to set the value of your DropDown (and it needs to be the hidden Value, not the Index or the Text, you can do something like this within the same module so you have the myNameSpace constant set up. Let's say you want to set the constant value "xyzvalue"
Sub populateDropdown1Element()
With ActiveDocument.CustomXMLParts.SelectByNamespace(myNameSpace)(1)
.SelectSingleNode("/ns0:ccvalues1[1]/ns0:dropdown1[1]").Text = "xyzvalue"
End With
End Sub
Of course if the source document has the same mappings, you can get the value of the source document dropdown from the same element in the source document's XML. The fact is that if you have the same XML, same mappings etc., ideally you should be able to replace the entire CustomXMLPart in the target document by the one from the "source" document. One of the reasons CustomXMLParts were invented was to allow people using the Office Open XML SDK to do exactly that. Unfortunately it does not work in VBA with the document open because Word tends to disconnect the Content Controls from the part.
But what you can do is iterate all the Element and Attribute nodes (for example) and replace the text in the target by the text from the source. Like this:
' You would need to pass in a reference to the document you want to get your data *from*
Sub replaceXML(sourceDocument As Word.Document)
Dim s As String
Dim cxn As Office.CustomXMLNode
Dim sourcePart As Office.CustomXMLPart
' You still need that definition of "myNameSpace"
Set sourcePart = sourceDocument.CustomXMLParts.SelectByNamespace(myNameSpace)(1)
With ActiveDocument
For Each cxn In .CustomXMLParts.SelectByNamespace(myNameSpace).Item(1).SelectNodes("//*[not(*)] | //#*")
cxn.Text = sourcePart.SelectSingleNode(cxn.XPath).Text
Next
End With
End Sub
What does "//*[not(*)] | //#*" select? Well, "//*[not(*)]" selects leaf Elements (including Elements that have attributes), "//#*" selects all attributes (which are always leaf nodes) and | is basically "Or" or "union".
Most custom xml I have seen in Word only stores data in Elements, and in that case you would only need "//*[not(*)]"

Related

VBA Word Insert text dynamically - Problems with ContentControl - Alternatives?

I am struggling quite a bit with ContentControl(s) in my Word VBA project.
There are a number of content control text fields which all have the same name (they have the same name because at the beginning the total number of required fields is not known, so I copy and paste the fields as many times as required). Now I want to loop through the content control fields and change the name of the fields based on the index of the individual items (e.g. first field in the document = "One", second field in the document = "two" and so on).
However, as mentioned in other threads, the index of the content control element does not correspond to its position in the document (I do not know, what it corresponds to).
Thus, instead of getting the fields in order, I get e.g. "four" --> "one" --> "three" --> "two" (or any other possible combination).
The content of the fields is coming from UserForm TextBoxes. The text boxes are named Text_Box_1 to Text_Box_4:
Private Sub Test() 'Note: The actual code is more complex, this is just to demonstrate my problem.
Dim i As Integer
UserForm1.TextBox1 = "one"
UserForm1.TextBox2 = "two"
UserForm1.TextBox3 = "three"
UserForm1.TextBox4 = "four"
For i = 1 To 4 - 1 'Since there are four text boxes in the UserForm in this example, the text snippet containing the text field gets copied and pasted three times; Note: Here the number of textboxes is pre-determined and fixed, in the actual project, it is variable.
ActiveDocument.Bookmarks(Index:="Copy").Range.Copy '
ActiveDocument.Bookmarks(Index:="Paste").Range.Paste
Next i
For i = 1 To 4 'This code is supposed to loop through the four content control text fields and insert text from the corresponding UserForm text box. However, content control text field 1, unfortunately does no correspond to UserForm.TextBox1 for some reason.
ActiveDocument.SelectContentControlsByTitle("Number").Item(i).Range.Text = UserForm1.Controls("TextBox" & i)
Next i
End Sub
Before running the code
After runnning the code
Is there any way to name to content control fields in the right order?
If not, what would be an alternative method to achieve my goals.
I think legacy text fields are not an option, since the document has to be protected; I have not looked into ActiveX text fields too much; Text boxes (shapes) might be another option, but they might have their own drawbacks.
It is really frustrating that the content control fields are behaving so weirdly and that something seemingly very simple and straight-forward can be so complicated (at least for me).
edit: Fixed a typo in the title.
Rather than use copy and paste I would insert the required text and content controls in my routine, something like this.
Private Sub Test()
Dim i As Integer
UserForm1.TextBox1 = "one"
UserForm1.TextBox2 = "two"
UserForm1.TextBox3 = "three"
UserForm1.TextBox4 = "four"
Dim cc As ContentControl
Dim rng As Range
Dim ccLocation As Range
For i = 4 To 1 Step -1 'Insert in reverse order to ensure that they are correct in the document
Set rng = ActiveDocument.Bookmarks("Paste").Range
rng.InsertAfter Text:="Number: "
rng.Collapse wdCollapseEnd
Set ccLocation = rng.Duplicate
rng.InsertAfter vbCr & "----------------------------------------" & vbCr
Set cc = ccLocation.ContentControls.Add(wdContentControlText)
cc.Range.Text = UserForm1.Controls("TextBox" & i).Text
cc.Title = "Number" & i
Next i
End Sub
If you cannot delete the existing content and must work with what you have then you could use the following:
Private Sub Test()
Dim i As Integer
UserForm1.TextBox1 = "one"
UserForm1.TextBox2 = "two"
UserForm1.TextBox3 = "three"
UserForm1.TextBox4 = "four"
ActiveDocument.SelectContentControlsByTitle("Number").Item(i).Range.Text = UserForm1.Controls("TextBox1").Text
Dim cc As ContentControl
Dim rng As Range
Dim ccLocation As Range
For i = 4 To 2 Step -1 'Insert in reverse order to ensure that they are correct in the document
Set rng = ActiveDocument.Bookmarks("Paste").Range
rng.InsertAfter Text:="Number: "
rng.Collapse wdCollapseEnd
Set ccLocation = rng.Duplicate
rng.InsertAfter vbCr & "----------------------------------------" & vbCr
Set cc = ccLocation.ContentControls.Add(wdContentControlText)
cc.Range.Text = UserForm1.Controls("TextBox" & i).Text
cc.Title = "Number" & i
Next i
End Sub

How can I set the order of columns in Outlook E-Mail Table view through VBA?

I use Outlook 2019 to manage ten (10) email accounts
I prefer the single-line table view
GOAL: columns equally arranged on ALL mail folders
show columns importance, icon, status, attachment, from, subject, date, size with same width and order on all inboxes and subfolders (expect the "sent" folders where I want "to" instead of "from").
Existing partial solution:
I have a VBA script (see below) to read the formatting (which is in Outlook.Folder.CurrentView.ViewFields) of a "reference folder" (where I manually arrange things) and then apply it to the currently selected folders
it works for column type, width and name
One problem remains:
this does NOT influence the ordering
everything is set, but the order is not same as the order in the configuration
Question: How can I influence the column order of the Outlook.Folder.CurrentView.ViewFields through VBA?
Sub ApplyReferenceColumnFormatToSelectedFolder()
Dim myNamespace As Outlook.NameSpace
Set myNamespace = Application.GetNamespace("MAPI")
' read ViewFields of reference folder into dictionary
Dim refFolder As Outlook.Folder
Set refFolder = myNamespace.Folders.Item("myaccount#myprovider.someTLD").Folders.Item("Posteingang")
Dim refFields As Dictionary
Set refFields = New Dictionary
Dim thisField As Dictionary
' iterate over all fields and read the relevant config data
' result is in two-dimensional dictionary
I = 1
For Each refField In refFolder.CurrentView.ViewFields
Set thisField = New Dictionary
thisField("Label") = refField.ColumnFormat.Label
thisField("ViewXMLSchemaName") = refField.ViewXMLSchemaName
thisField("Width") = refField.ColumnFormat.Width
thisField("FieldFormat") = refField.ColumnFormat.FieldFormat
Set refFields(I) = thisField
I = I + 1
Next
' now "copy" this config to currently selected folder
Set curFolder = Application.ActiveExplorer.CurrentFolder
Set curView = curFolder.CurrentView
' remove all but one ViewFields
' (ideally, would remove all but there needs to be at least one remaining)
oC = curView.ViewFields.Count
If (oC > 1) Then
For I = oC To 2 Step -1
curView.ViewFields.Remove (I)
Next
End If
curView.Apply
' now, set the desired configuration
With curView
' set single-line table view without preview
.AutoPreview = olAutoPreviewNone
.MultiLine = olAlwaysSingleLine
.ShowFullConversations = True
'.Apply
' iterate over the columns form reference folder
I = 1
For Each refField In refFields
Set thisField = refFields(refField)
' add field
' note: can fail, if field of same type already exists
' then, we can just "resume next" without adding :-)
On Error Resume Next
.ViewFields.Add (thisField("ViewXMLSchemaName"))
.Apply
' finally, set the relevant properties
.ViewFields.Item(I).ColumnFormat.Label = thisField("Label")
.ViewFields.Item(I).ColumnFormat.Width = thisField("Width")
.ViewFields.Item(I).ColumnFormat.FieldFormat = thisField("FieldFormat")
.Apply
I = I + 1
Next
End With
curView.Apply
End Sub
The Microsoft Documentation has the following remark:
In a table view, the order of ViewField objects in the ViewFields
collection is not the same as the order that field columns are
displayed in the table view. A workaround to obtain the column order
is to parse the string returned by the View.XML property.
So it doesn't look like you can influence the column order simply by how you arrange the ViewFields collection. It look likes you would need to manipulate the XML.
Have you considered simply creating your desired view, locking that view via VBA (to prevent accidentally altering it), and then applying that view to the various folders?
Sorry that this isn't much of an answer, more of a remark, but it was too long to put in a comment.

How do I deactivate and reactivate several geometrical sets and objects automatically?

I wrote a macro that hides everything in several geometrical sets and the objects and geometrical sets in these first sets except one specific branch. I use this for saving a defined object of a huge and complicated specification tree as a STP file. (See attached below.)
(Small complication in this “Hide_and_Save” macro: adding bodies to my hide-selection works well but for my show-selection it didn’t work the same way. Why would this happen?)
I also wrote a macro that does iterative adjustments. For the iterations I use a Do While Loop and some parameters and measurements. To update these values, I have to update the part/object in every cycle. But there are some construction elements that issue errors until the loop is successfully completed. Therefore I deactivate all the geometrical sets that I don’t need for the iterations (inclusively all children) and later I reactivate them manually.
My goal is to improve automation, so I tried to use my “Hide_and_Save” macro for deactivation and reactivation. This didn’t work. When I record the process, each object is listed in a separate line and deactivated. Since there are more than 350 elements, I would like to avoid this.
How do I deactivate all subelements in a geometrical set (preferably with children) without addressing each element individually?
Attribute VB_Name = "Hide_and_Save"
'_______________________________________________________________________________________
'Title: Hide_and_Save
'Language: catvba
'_______________________________________________________________________________________
Sub CATMain()
'---------------------------------------------------------------------------------------
'Select active Part/Document
Dim myDocument As Document
Set myDocument = CATIA.ActiveDocument
Dim myPart As part
Set myPart = CATIA.ActiveDocument.part
'--------------------------------------------------------------
' Enter file path
Dim filepath As String
filepath = InputBox("Please select memory location", "Input filepath", "...")
If filepath = "" Then 'cancle, abort or empty input
MsgBox "No valid input / cancle !"
Exit Sub
End If
'--------------------------------------------------------------
' Hide/show Objects of Part/Products and save as STEP
' Update Model
CATIA.ActiveDocument.part.Update
' Deklaration of Selections and Properties
Dim selectionShow, selectionHide As Selection
Set selectionShow = myDocument.Selection
Set selectionHide = myDocument.Selection
Dim visPropertySetShow, visPropertySetHide As VisPropertySet
Set visPropertySetShow = selectionShow.VisProperties
Set visPropertySetHide = selectionHide.VisProperties
' Definition of the collection of geometric sets - HybridBodies
Dim hybridBodiesInPart, hybridBodiesInProcess As HybridBodies
Dim hybridBodiesInRS, hybridBodiesInHuelle As HybridBodies
' Definition of individual geometric sets - HybridBody
Dim hybridBodyInPart, hybridBodyProcess, hybridBodyInProcess As HybridBody
Dim hybridBodyRS, hybridBodyInRS As HybridBody
Dim hybridBodyHuelle, hybridBodyInHuelle As HybridBody
' Definition of the collection of 3D-objects - HybridShapes
Dim hybridShapesInHuelle As HybridShapes
' Definition of individual 3D-objects - HybridShape
Dim hybridShapeInHuelle, hybridShapeForm As HybridShape
' Hide objects
Set hybridBodiesInPart = myPart.HybridBodies
For Each hybridBodyInPart In hybridBodiesInPart
selectionHide.Add hybridBodyInPart
Next
Set hybridBodyProcess = hybridBodiesInPart.Item("Process")
Set hybridBodiesInProcess = hybridBodyProcess.HybridBodies
For Each hybridBodyInProcess In hybridBodiesInProcess
selectionHide.Add hybridBodyInProcess
Next
Set hybridBodyHuelle = hybridBodiesInProcess.Item("Huelle")
Set hybridBodiesInHuelle = hybridBodyHuelle.HybridBodies
For Each hybridBodyInHuelle In hybridBodiesInHuelle
selectionHide.Add hybridBodyInHuelle
Next
Set hybridShapesInHuelle = hybridBodyHuelle.HybridShapes
For Each hybridShapeInHuelle In hybridShapesInHuelle
selectionHide.Add hybridShapeInHuelle
Next
Set hybridShapeForm = hybridShapesInHuelle.Item("Form")
visPropertySetHide.SetShow 1 'hide
selectionHide.Clear
' Show objects
selectionShow.Add hybridBodyProcess
selectionShow.Add hybridBodyHuelle
selectionShow.Add hybridShapeForm
visPropertySetShow.SetShow 0 'show
selectionShow.Clear
' Data export as STP
stepAnswer = MsgBox("Should the displayed elements be saved as STEP?", 3 + 0, "Export: Form")
If stepAnswer = 6 Then
myDocument.ExportData filepath & "Form" & ".stp", "stp"
ElseIf stepAnswer = 3 Or stepAnswer = 2 Then 'cancle or abort
MsgBox "cancle !"
Exit Sub
End If
'---------------------------------------------------------------------------------------
MsgBox "Finished !" & vbCrLf & s
End Sub
(Usually I work with Generative Shape Design and use VBA for Macros.)
Each feature has an "Activity" parameter aggregated to it.
Dim oShape as HybridShape
For Each oShape In oGS.HybridShapes
Dim oActivity as Parameter
Set oActivity = oPart.Parameters.SubList(oShape,False).Item("Activity")
Call oActivity.ValuateFromString("False")
Next
Let me add that screwing with Activity of features is not a best practice. I NEVER do this myself. If you have access KBE (Specifically Knowledge Advisor Workbench) you can probably do what you want with Rules/Actions/Reactions, less coding and have a more robust model in the end.

How to copy reference to the active list number in Word?

I have a lot of lists in my document with numbers that look like "1.3.2" and I want to automate the process of creating a cross-references to the list elements.
I'm trying to make a macro that will:
detect the list element, cursor is positioned at;
create a cross reference to the list element with number as a reference text (i.e. "1.3.2");
put it into the clipboard;
make "LCtrl+C" hotkey launch that macro when cursor is positioned at the list number (optional: only for the lists with declared style(s)).
How do I achieve that with VBA?
After looking at the object model and how Word behaves I think you can manage something, but perhaps not exactly the way you envisioned. The problem lies with the Numbered Items, which seem to be oriented to captions rather than numbered lines... In any case, when a cross-reference is inserted via the dialog box to a "Numbered item" Word does create a bookmark and then reference that. So my suggestion emulates that behavior, as in the following code snippet.
What you'll need/want to do is maintain a "counter" for incrementing the bookmark name (or you could generate GUIDs, the way Word does). My demo has the bookmark name hard-coded.
This example sets the hidden bookmark at the beginning of the paragraph where the current selection is. It then inserts a cross-reference, extends the Range to include the cross-reference (since the method does not return a range or object) and cuts it to the clipboard. The user can then paste it wherever he wants.
Sub InsertThenCopyCrossRef()
Dim rng As word.Range, rngBkm As word.Range
Dim bkm As word.Bookmark
Dim sMyRef As String
sMyRef = "_MyRef_1" 'a counter or something to make name unique!
Set rng = Selection.Range
Set rngBkm = rng.Duplicate.Paragraphs(1).Range
rngBkm.Collapse wdCollapseStart
Set bkm = ActiveDocument.Bookmarks.Add(sMyRef, rngBkm)
rng.InsertCrossReference wdRefTypeBookmark, wdNumberFullContext, sMyRef
rng.MoveEnd wdWord, 1
rng.Fields(1).Cut
'rng.Select
End Sub
I've tinkered around for the fun of it and long story short: I don't think you will manage to do that. Reason: this is the code for creating a cross reference to a numbered item in VBA:
Set r = Selection.Range
r.InsertCrossReference ReferenceType:="Numbered item", _
ReferenceKind:=wdNumberRelativeContext, ReferenceItem:="5", _
InsertAsHyperlink:=True, IncludePosition:=False, SeparateNumbers:=False, _
SeparatorString:=" "
Trouble here is the ReferenceItem:="5". When I recorded this, it was simply the fifth numbered item regardless of its list level.
So all you have to do now is to find a way to identify a numbered item as the nth numbered item in your document.
If you can solve that, you can assign a key combination to copy a reference to the current list item like this:
Sub CopyReference()
Dim r As Range
Dim dObject As DataObject
Set dObject = New DataObject
Set r = Selection.Range
r.InsertCrossReference ReferenceType:="Nummeriertes Element", _
ReferenceKind:=wdNumberRelativeContext, ReferenceItem:="5", _
InsertAsHyperlink:=True, IncludePosition:=False, SeparateNumbers:=False, _
SeparatorString:=" "
dObject.SetText r.Paragraphs(1).Range.Fields(1).Code
r.Paragraphs(1).Range.Fields(1).Delete
dObject.PutInClipboard
End Sub
And another key combination to paste your reference like this:
Sub pasteField()
Dim fld As Field, dObject As DataObject
Dim gg
Set fld = ActiveDocument.Fields.Add(Selection.Range, wdFieldRef)
Set dObject = New DataObject
dObject.GetFromClipboard
gg = dObject.GetText
fld.Code.Text = gg
fld.Update
End Sub
As you can see, I haven't actually copied the cross reference field but only its code.

Add a content control after an existing content control in word 2010 using vba

A little more detail:
I am inserting (lots of) documents with content controls into a single document.
One of the content controls in each doc is a title control (linked to document property), which naturally receives the same value as the destination document's title on insert.
Renaming the control's title and or tag, using word or vba does not fix the problem (weird!)
My proposed solution is to create a new control with a different name, copy across the .range.text from the original title control and then delete the title control.
I have a loop which goes through all the files that need changing which works fine. However, whatever I seem to do, any new controls that I create appear at the beginning of the document and not in the correct place (there is a control with a code for the document before it).
Ideas? As an aside is there any logical reason why changing the control names doesn't work?
Current code:
Sub FieldChanger()
Dim docCur As Document
Dim strCurPath As String
Dim strCurFile As String
Dim rngTitle As Range
Dim strTitle As String
Dim ccName As ContentControl
strCurPath = "C:\Users\User\Desktop\BGS\Final\"
strCurFile = Dir(strCurPath & "*.docx")
Do While strCurrentFile <> ""
Set docCur = Application.Documents.Open(strCurPath & strCurFile)
With docCur.ContentControls
.Item(1).LockContents = False //Unlock outer content control
Set rngTitle = .Item(3).Range
strTitle = rngTitle.Text
rngTitle = rngTitle.Move(wdCharacter, 1)
ccName = rngTitle.ContentControls.Add(wdContentControlRichText) //This line throws a 4198 error
ccName.Title = "ccName"
ccName.Tag = "ccName"
ccName.Range = strTitle
ccName.LockContentControl = True
.Item(3).LockContentControl = False
.Item(3).Delete
.Item(1).LockContents = True //Lock outer content control
End With
docCur.Save
docCur.Close
strCurFile = Dir
Loop
End Sub
As an aside is there any logical reason why changing the control names doesn't work?
The Content Control (CC) name is just a name. Renaming the CC from "Title" doesn't change where Word gets the content from. Nor would naming a CC as "Title" cause Word to put the document's title string in the CC. If you create an empty document, insert the Title document property (as a CC) and look at the value of
activedocument.ContentControls(1).XMLMapping.XPath
you will probably see the value
/ns1:coreProperties[1]/ns0:title[1]
This is what tells Word that it needs to put the value of the Title builtin document property in the CC, and where to go to get it. You can link your own plain text CCs to builtin properties using the same mechanism, or you can link them to nodes in "Custom XML parts" of your own. But they don't have to be linked to anything.
As for the code, how about something more like this (NB, I have also changed "strCurrentFile" to strCurFile). I wondered whether you really need to re-insert the CC value as a new CC (i.e. why not just remove the CC and leave its existing value there) but have assumed that you need the CC there.
NB, as a general rule in VBA you need to use the Set keyword when setting the value of objects such as range variables and CCs. In theory you should also set objects to Nothing (e.g. Set rngTitle = Nothing) when you have finished with them. I haven't added that stuff here. In VB.NET you don't need to do either of those things.
Dim docCur As Document
Dim strCurPath As String
Dim strCurFile As String
Dim rngTitle As Range
Dim strTitle As String
Dim ccName As ContentControl
strCurPath = "C:\a\test\"
strCurFile = Dir(strCurPath & "*.docx")
Do While strCurFile <> ""
Set docCur = Application.Documents.Open(strCurPath & strCurFile)
With docCur.ContentControls
.Item(1).LockContents = False 'Unlock outer content control
Set rngTitle = .Item(3).Range
strTitle = rngTitle.Text
' we need the following line to ensure that deleting the range
' does not remove the CC prematurely
.Item(3).Temporary = False
rngTitle.Delete
rngTitle.Collapse wdCollapseStart
' Delete the control here instead of later
.Item(3).LockContentControl = False
.Item(3).Delete
Set ccName = rngTitle.ContentControls.Add(wdContentControlRichText)
ccName.Title = "ccName"
ccName.Tag = "ccName"
ccName.Range = strTitle
ccName.LockContentControl = True
.Item(1).LockContents = True 'Lock outer content control
End With
docCur.Save
docCur.Close
strCurFile = Dir
Loop
Comment consolidation...
There are addins that may help, e.g. the databinding toolkit at cctw.codeplex.com (not checked that link recently)