Word toc show levels adjustment using VBA - vba

I'm fairly new to VBA in general, but currently I'm working on publishing a document utilizing IBM's Rational Publishing Engine which publishes a document out of DOORS (Dynamic Object Oriented Requirements System). After publishing there are a series of macros that are utilized to expandOLEs, merge paragraphs, centerFigures, etc. I'm looking to add a macro that will adjust my table of contents to only show levels 2. I was thinking something like the below would work, but have not had much success.
Sub Tocadjust()
Dim toc As TableOfContents
Dim tocEntry As Field
Set toc = ActiveDocument.TableOfContents(1)
For Each tocEntry In toc.Range.Fields
tocEntry.Select
toc.UpperHeadingLevel = 1
toc.LowerHeadingLevel = 2
Next
End Sub

Tables of Contents in Word are, themselves, fields.
They do not, generally, contain fields. They do have switches.
Running the following code adds a switch limiting the TOC to levels 1 and 2.
Sub TocAdjust()
' Charles Kenyon
' Limit first TOC to levels 1 and 2
'
ActiveDocument.TablesOfContents(1).LowerHeadingLevel = 2
ActiveDocument.TablesOfContents(1).UpperHeadingLevel = 1
End Sub
This will throw an error if there is no Table of Contents field. Note that Tables of Figures also use the TOC field.
Here is what a TOC field looks like before and after running the macro.
Your code was trying to act on "entries" rather than on the field in general.

Related

Saving Custom Document Properties in a Loop

I'm trying to save the values of data that have been input into my form. There are a total of about 50 different fields to save across 5 different agents, so I loaded the data into arrays.
I've tried saving the fields in a loop, but it doesn't seem to work in a loop, only if each field has a separate line, which is a lot of code and messy. The Ag1Name, Ag2Name and Ag3Name are the names of my textboxes that the user enters to populate the form.
Sub LoadAndSaveData()
NumberofAgents = 3
Dim AgentName(3) as String
AgentName(1) = Ag1Name.Value
AgentName(2) = Ag2Name.Value
AgentName(3) = Ag3Name.Value
For Count = 1 To NumberOfAgents
With ActiveDocument.CustomDocumentProperties
.Add Name:="AgentName" & Count, LinkToContent:=False, Value:=AgentName(Count), Type:=msoPropertyTypeString
End With
Next Count
End Sub
The data doesn't get saved to the Custom Document Properties when the code is set up in a loop like the above. Since there are so many values to save and all the data is already in arrays, I would much prefer to use a loop rather than write out a separate line of code for all ~50 of the values. It does seem to work when each field is saved in a separate line of code.
I think this would probably get what you want. You don't really need to count the document properties first, only increment with the ones you want to update. Hopefully the only document properties you want contain the name AgentName in it.
ReDim AgentName(0) As String
Dim P As Long
For Each c In ThisDocument.CustomDocumentProperties
If InStr(1, c.Name, "AgentName", vbTextCompare) > 0 Then
ReDim Preserve AgentName(P)
AgentName(P) = c.Value
P = P + 1
End If
Next c
As a guest I cannot post a comment here, but the code you gave works OK here.
However, there is a problem with creating legacy custom document properties programmatically, because doing that does not mark the document as "changed". When you close the document, Word does not necessarily save it and you lose the Properties and their values.
However, if you actually open up the Custom Document Property dialog, Word does then mark the document as "changed" and the Properties are saved.
So it is possible that the difference between your two scenarios is not the code, but that in one scenario you have actually opened the dialog box to check the values before closing the document and in the other you have not.
If that is the case, here, I was able to change this behaviour by adding the line
ActiveDocument.Saved = False
after setting the property values.
If you do not actually need the values to be Document Properties, it might be better either to use Document Variables, which are slightly easier to use since you can add them and modify them with exactly the same code, or perhaps by storing them in A Custom XML Part, which is harder work but can be useful if you need to extract the values somewhere where Word is not available.
You can make this even easier by looping the controls on the UserForm, testing whether the control name contains "Ag" and, if it does, create the Custom Document Property with the control's value - all in one step.
For example, the following code sample loops the controls in the UserForm. It tests whether the controls Name starts with "Ag". If it does, the CustomDocumentProperty is added with that control's value.
Sub LoadAndSaveData()
Dim ctl As MSForms.control
Dim controlName As String
For Each ctl In Me.Controls
controlName = ctl.Name
If Left(controlName, 2) = "Ag" Then
With ActiveDocument.CustomDocumentProperties
.Add Name:=controlName, LinkToContent:=False, value:=ctl.value, Type:=msoPropertyTypeString
End With
End If
Next
End Sub
I feel a little stupid... I just realized that the reason that the code wasn't working was that the variable NumberofAgents was not being calculated correctly elsewhere in my code. I've got it working now. Thanks for your thoughts!

How do you save data input into a macro so that the input form can be populated with the data next time it runs?

I need to save data that was input into a Microsoft Word form in VBA after the macro terminates. I have a form that has about 40 fields with names, addresses, phone numbers etc and I don't want the user to have to input everything again in case they realize that they made a mistake and need to change 1 item. Currently, the macro deletes all the data that was input into the form when the form closes.
I've looked around on forums and Google but haven't had any luck.
This code brings up my input form from a word document with a command button:
Private Sub CommandButton1_Click()
InputForm.Show vbModeless
End Sub
This code closes the form:
Private Sub CloseForm_Click()
Dim Closing As Integer
Closing = MsgBox("This will exit the form and erase all the data that has been input. You may want to review the documents to ensure they were generated correctly before closing the form. Click Yes to proceed to close the form or No to go back to the form.", vbYesNo + vbQuestion, "Exit Confirmation")
If Closing = vbYes Then
Unload InputForm 'This closes the Input Form and returns the user to Word
End If
End Sub
The macro does not save the information input into the form. Whenever it closes, all the information is erased; I want it saved.
Options for saving data in the document
Document.Variables This has been available in Word since the very early days and is available in all versions since Word 2.0 (early 1990s). It's simply a place to store any number of strings, associated with an identifier (name). In essence, a key-value pair. Important to note: It must be a pair, there cannot be a key without a value and a value cannot be a zero-length string. Assigning a value to a name creates the Variable; deleting the value removes the Variable. These are not visible to the user.
Example:
Document.Variables("Name").Value = "John Doe"
Document.CustomDocumentProperties Similar to Variables but data-types other than strings are available. There is a limit to the length of the data that can be stored. They are visible to the user through the "Properties" interface. They must be explicitly created / destroyed. This was introduced in, I believe, Word 97.
Example:
Document.CustomDocumentProperties.Add(Name As String, LinkToContent As Boolean, _
[Type], [Value], [LinkSource])
CustomXMLParts These are XML "pages" saved in the docx zip package. This is a good way to save data, and it's easily accessible from the closed file. The capability was introduced in Word 2010, so won't be available in older versions nor in doc file formats, only docx.
The coding is more complex than for Variables or Properties.
The usual way to work with this in the described scenario is to create the CustomXMLPart in the document (or template - documents created from the template should carry over the CXP). Then all the code needs to do is read/write from the xml nodes already available. If the VBA coder has no background in XML parsing there will be a steep learning curve.
There are many ways to go about reading and writing, here's a snippet that demonstrates the basics of reading, to give an idea of what's involved:
Sub ReadCXP()
Dim cxp As Office.CustomXMLPart, CXPs As Office.CustomXMLParts
Dim doc As Word.Document
Dim sUri As String
Set doc = ActiveDocument
'object, not collection, because ID is unique and can return only one
'ID is unique to each CustomXMLPart - this is won't work as it stands!
Set cxp = doc.CustomXMLParts.SelectByID("{32B20BB8-F4FB-47D6-9DF4-FFAF5A1D8C18}")
Dim xmlNds As Office.CustomXMLNodes
Dim xmlNd As Office.CustomXMLNode
Set xmlNds = cxp.SelectSingleNode("trees").ChildNodes
For Each xmlNd In xmlNds
Debug.Print xmlNd.XML
Next
End Sub

How to find and disable a content control by tag to delete it and its contents?

I have the unfortunate task of being forced to design a Word-based electronic production card for the unit at my company, even though I've never worked with VBA. I would much rather have done this in Excel since I wouldn't have to wrestle with content control and hard-to-find locations in various tables over the pages, but the company's documentation-system forces this particular one to be in Word.
My issue is that for proper form of the production card I need to use tables, and I need the production card to be dynamic to limit its size to what operations that are relevant for a specific order. My chosen solution is to create a full form, and to use a user form/prompt where they can choose which parts to use and which parts to ommit, and the ommitted ones will then be deleted. Part of reason for the solution is because that is how their previous (and Excel-based) production card works, so it would make it more familiar for the end user.
Because MS Word is finicky I need to use content control within these tables to not have the end user accidentally destroy half of it, but after a full workday I still cannot figure out how find and shut off the content control of the cells in tables that I want to delete. I do have the content controls tagged since that seems like the only reasonable way to find them.
This is my current code for the subprocedure, but for some reason I cannot get the ID through the ccID line, even though I have verified that the string supplied as argument is correct.
Private Sub DeleteCCByTag(ccTag As String)
Dim cc As ContentControl
Dim ccID As String
ccID = ThisDocument.SelectContentControlsByTag(ccTag).Item(1).ID
'MsgBox ccID 'Debug prompt
Set cc = ThisDocument.ContentControls(ccID)
cc.LockContentControl = False
cc.LockContents = False
cc.Delete (False)
End Sub
First of all- your code is working find for me but...
ContentControls tag is case-sensitive which could be a problem in your situation
You could solve your problem without searching for ID value in this way:
Private Sub DeleteCCByTag_Alternative(ccTag As String)
Dim cc As ContentControl
Set cc = ThisDocument.SelectContentControlsByTag(ccTag).Item(1)
With cc
.LockContentControl = False
.LockContents = False
.Range.Delete 'to delete CC content
.Delete (False)
End With
End Sub
CC.Delete in your code deletes only ContentControl objects itself but not its content. To delete content you need to add additional line which I did in my code above.
I would add this as a comment to KazJaw's answer but I don't have the rep.
According to Microsoft's documentation, if you pass True to the Delete method it removes both the content control and its contents.
So: just get the Item as KazJaw showed, without jumping through the hoop of getting its ID:
Set cc = ThisDocument.SelectContentControlsByTag(ccTag).Item(1)
then call .Delete(True) on it.

Get the number of pages in a Word document

I am making lots of changes to a Word document using automation, and then running a VBA macro which - among other things - checks that the document is no more than a certain number of pages.
I'm using ActiveDocument.Information(wdNumberOfPagesInDocument) to get the number of pages, but this method is returning an incorrect result. I think this is because Word has not yet updated the pagination of the document to reflect the changes that I've made.
ActiveDocument.ComputeStatistics(wdStatisticPages) also suffers from the same issue.
I've tried sticking in a call to ActiveDocument.Repaginate, but that makes no difference.
I did have some luck with adding a paragraph to the end of the document and then deleting it again - but that hack seems to no longer work (I've recently moved from Word 2003 to Word 2010).
Is there any way I can force Word to actually repaginate, and/or wait until the repagination is complete?
I just spent a good 2 hours trying to solve this, and I have yet to see this answer on any forum so I thought I would share it.
https://msdn.microsoft.com/en-us/vba/word-vba/articles/pages-object-word?f=255&MSPPError=-2147217396
That gave me my solution combined with combing through the articles to find that most of the solutions people reference are not supported in the newest versions of Word. I don't know what version it changed in, but my assumption is that 2013 and newer can use this code to count pages:
ActiveDocument.ActiveWindow.Panes(1).Pages.Count.
I believe the way this works is ActiveDocument selects the file, ActiveWindow confirms that the file to be used is in the current window (in case the file is open in multiple windows from the view tab), Panes determines that if there is multiple windows/split panes/any other nonsense you want the "first" one to be evaluated, pages.count designates the pages object to be evaluated by counting the number of items in the collection.
Anyone more knowledgeable feel free to correct me, but this is the first method that gave me the correct page count on any document I tried!
Also I apologize but I cant figure out how to format that line into a code block. If the mods want to edit my comment to do that be my guest.
Try (maybe after ActiveDocument.Repaginate)
ActiveDocument.BuiltinDocumentProperties(wdPropertyPages)
It is causing my Word 2010 to spend half-second with "Counting words" status in status bar, while ActiveDocument.ComputeStatistics(wdStatisticPages) returns the result immediately.
Source: https://support.microsoft.com/en-us/kb/185509
After you've made all your changes, you can use OnTime to force a slight delay before reading the page statistics.
Application.OnTime When:=Now + TimeValue("00:00:02"), _
Name:="UpdateStats"
I would also update all the fields before this OnTime statement:
ActiveDocument.Range.Fields.Update
I found a possible workaround below, if not a real answer to the topic question.
Yesterday, the first ComputeStatistics line below was returning the correct total of 31 pages, but today it returns only 1.
The solution is to get rid of the Content object and the correct number of pages is returned.
Dim docMultiple As Document
Set docMultiple = ActiveDocument
lPageCount = docMultiple.Content.ComputeStatistics(wdStatisticPages) ' Returns 1
lPageCount = docMultiple.ComputeStatistics(wdStatisticPages) ' Returns correct count, 31
ActiveDocument.Range.Information(wdNumberOfPagesInDocument)
This works every time for me. It returns total physical pages in the word.
I used this from within Excel
it worked reliably on about 20 documents
none were longer than 20 pages but some were quite complex
with images and page breaks etc.
Sub GetlastPageFromInsideExcel()
Set wD = CreateObject("Word.Application")
Set myDoc = wD.Documents.Open("C:\Temp\mydocument.docx")
myDoc.Characters.Last.Select ' move to end of document
wD.Selection.Collapse ' collapse selection at end
lastPage = wD.Selection.Information(wdActiveEndPageNumber)
mydoc.close
wd.quit
Set wD = Nothing
End Sub
One problem I had in getting "ComputeStatistics" to return a correct page count was that I often work in "Web Layout" view in Word. Whenever you start Word it reverts to the last view mode used. If Word was left in "Web Layout" mode "ComputeStatistics" returned a page count of "1" page for all files processed by the script. Once I specifically set "Print Layout" view I got the correct page counts.
For example:
$MSWord.activewindow.view.type = 3 # 3 is 'wdPrintView', 6 is 'wdWebView'
$Pages = $mydoc.ComputeStatistics(2) # 2 is 'wdStatisticPages'
You can use Pages-Object and its properties such as Count. It works perfect;)
Dim objPages As Pages
Set objPage = ActiveDocument.ActiveWindow.Panes(1).Pages
QuantityOfPages = ActiveDocument.ActiveWindow.Panes(1).Pages.Count
Dim wordapp As Object
Set wordapp = CreateObject("Word.Application")
Dim doc As Object
Set doc = wordapp.Documents.Open(oFile.Path)
Dim pagesCount As Integer
pagesCount = doc.Content.Information(4) 'wdNumberOfPagesInDocument
doc.Close False
Set doc = Nothing

How to remove the recent document history in Excel Ribbon using VBA

How to remove the recent document history in Excel Ribbon using VBA.
I am using the code below, but it doesn't seems to work.
Sub Button1_Click()
For i = 1 To Application.RecentFiles.Count - 1
Application.RecentFiles(i).Delete
Next i
End Sub
Thanks ...
To clear the list of recently used files, and not mess with the user's settings, the following code will work:
originalSetting = Application.RecentFiles.Maximum
Application.RecentFiles.Maximum = 0
Application.RecentFiles.Maximum = originalSetting
This will remove the recent files and then reset the maximum number of recent files back to whatever the user had initially.
If you just want to remove them individually, you can step through them in reverse order to get the job done.
Dim i As Integer
For i = Application.RecentFiles.Count To 1 Step -1
Application.RecentFiles.Item(i).Delete
Next
You need to run from the bottom of the collection up, because as soon as you delete one of the entries from the RecentFiles collection, all of the indexes of the remaining files change. This way, each time through the loop, you are deleting the last item in the collection.
And also, since this collection is Base 1 instead of Base 0, the last item in the collection is Application.RecentFiles.Count rather than .RecentFiles.Count-1.
I just love all those little inconsistencies in Excel.. :)
There is no direct mechanism for hiding the most recently used file listing. It can be done, however, by setting the Application.RecentFiles.Maximum to zero (0).
For a detailed discussion, see Change the Ribbon in Excel 2007 by Ron de Bruin, and scroll down to the section titled "Dictator examples and Hide the MRU ('Most Recently Used') file list", with code provided by Jim Rech.
This can also be done manually. See: How to Clear and Delete Recent Documents List in Office 2007 (Word, Excel, PowerPoint).
-- Mike
To clear the activeworkbook from the list of recentfiles use this:
Sub DeleteFileFromRecentFiles()
Dim i As Integer
For Each RecentFile In Application.RecentFiles
If ActiveWorkbook.Name = RecentFile.Name Then
i = i + 1
Application.RecentFiles.Item(i).Delete
End If
Next
End Sub
Sincerely,
Richard