Setting different margins for every certain pages? - vba

I have a very large word file. In fact it has more than 500 pages. Now I want to set different left margins for every third page. Like page one, four, seven... have left margin 30
the rest have left margin 1. I try to do that by using vba like the following:
Sub SetMargin()
Set MyDoc = ActiveDocument
PageNum = MyDoc.Range.Information(wdNumberOfPagesInDocument)
For i = PageNum To 2 Step -1
Set Pos = MyDoc.GoTo(wdGoToPage, wdGoToAbsolute, i)
Pos.insertBreak wdSectionBreakContinuous
Next
For Each i In MyDoc.Sections
If (i.Index + 2) Mod 3 = 0 Then
i.PageSetup.LeftMargin = 30
End If
Next
End Sub
But the problem is the article contains some long sentences, so after running the program, those sentences break into two lines and some pages just cannot contain the whole section and it floats to the next page. Thus some pages have two different left margins, which really confuses me since I am new at Word and VBA programming. There must be some way to fix that. But I don't know how. Your help would be greatly appreciated.
Thanks for your help.

Related

Putting "Align at equals" into a macro

I'm working on a Word Macro to streamline doing my University Mathematics and Statistics Coursework. Basically, selecting a group of equations and running it changes font size, line height and formats the paragraph in the way that I want to be common to all my maths/equations sections. It's great, but there's one little bit which I still have to do "manually", so to speak, which is right clicking and selecting "Align at equals".
Now the reason I'm asking this here and not on Super User is that I think I've exhausted all ways of doing this at the "record macro" stage. I found out how to access the right click menu without right clicking and accessed the "align at equals" option during record. Nothing was recorded.
Truth be told I'd prefer to code the lot anyway as it gives me more control. So, I'll post my code here and if anyone knows what line(s) of code I need to add to get it to replicate the "align at equals" command I would be extremely grateful.
Sub Equationiser()
'
' Equationiser Macro
'
'
With Selection.ParagraphFormat
.SpaceBefore = 12
.SpaceAfter = 12
.LineSpacingRule = wdLineSpace1pt5
End With
Selection.Font.Size = 20
End Sub
So, ideally just before the "With Selection.ParagraphFormat" section there would be some kind of "AlignAtEquals" command or whatever is needed so that, on one keypress, I would be able to align all the equals, set the line height to 1.5, place a 12 point space before and after the paragraph and change the font size to 20.
My absolute ideal would also be to programatically select all equation boxes that are in the same block, as "align at equals" is notoriously fussy and finicky as to when it will execute. That might also mean there may be a try and catch needed depending on whether trying to run "align at equals" when it wouldn't normally be available from the right click menu would do nothing or cause an error.
Any help on these two implementations would be gratefully appreciated but I'd happily settle for just the first.
I've built a solution that should address your needs, based on:
Looking through the equation to find the equal-sign.
Get the location of that equal-sign
Use that location to set the AlignPoint property of the OMath object
that is your equation
Use this MSDN reference if you want to explore more
Sub Equationiser()
'
' Equationiser Macro
'
'
Dim equationCounter As Long, charLoc As Long, FormattedTextLoc As Long
With Selection
For equationCounter = 1 To .OMaths.Count:
FormattedTextLoc = 0
For charLoc = 1 To Len(.OMaths(equationCounter).Range.FormattedText):
FormattedTextLoc = FormattedTextLoc + Len(.OMaths(equationCounter).Range.FormattedText.Characters(charLoc))
If .OMaths(equationCounter).Range.FormattedText.Characters(charLoc) = "=" Then
.OMaths(equationCounter).AlignPoint = (FormattedTextLoc - 1)
Exit For
End If
Next charLoc
Next equationCounter
End With
With Selection.ParagraphFormat
.SpaceBefore = 12
.SpaceAfter = 12
.LineSpacingRule = wdLineSpace1pt5
End With
Selection.Font.Size = 20
End Sub
I've done some brief testing that from what I can see this should be able to manage several code-blocks, i.e. when selecting 2 code-blocks it will align-at-equals all equations in the first block, then align-at-equals all equations in the second block (both blocks are not aligned with each oter) - is this your desired outcome to your request: My absolute ideal would also be to programatically select all equation boxes that are in the same block, as "align at equals" is notoriously fussy and finicky as to when it will execute.

Word VBA: Get table that is on the display screen at the moment

Suppose you have a microsoft word file (.DOCX), you open it and start to view it, using the mouse scrolling, from page 1 towards the last page.
Then, suppose you see a table, for instance, on page 4.
Now I ask: is it possible for Word-VBA to say to you what is the index or name of that table that is on the screen at the moment, no matter where is cursor located?
I want word-VBA fullfil a table that I am seeing at that moment, no matter where the cursor is located.
I hope I was clear enough...
Word doesn't have a direct way to get what's visible on-screen. It can be calculated, sort of, but not with 100% accuracy.
The following code sample does the trick for me, on my machine. It may need some tweaking to work on a different set up.
The object model does return the co-ordinates of the application window (ActiveWindow, here), the height of that Window and the UsableHeight - the height of the actual document working space. That can be used to get an estimated position.
There's also a Windows API function equivalent - RangeFromPoint - for a Window object that returns the a Range in the document for the given screen co-ordinates.
This code calculates a left and top position for the start of the visible part of the document, as well as for the end of the visible document. (In my test, it was a bit more, but not much). It then checks whether there is one or more tables within that scope. If it is, it takes the first one ( Set tbl = rngTargetStart.Tables(1)) - this returns the object your code needs to work with. As a "bonus", the code prints the index number of the table in the document and the page it's on to the Immediate Window.
Sub CheckForTableOnPage()
Dim WordWindowTop As Long 'in points
Dim WordWindowLeft As Long 'in points
Dim windowUsableHeight As Long 'in points
Dim rngTargetStart As Range
Dim rngTargetEnd As Range
Dim pageNumberTarget As Long
Dim tbl As Table
WordWindowTop = ActiveWindow.height
WordWindowLeft = ActiveWindow.left
windowUsableHeight = ActiveWindow.UsableHeight
RibbonFactor = 200
Set rngTargetStart = ActiveWindow.RangeFromPoint(WordWindowLeft, WordWindowTop - windowUsableHeight)
Set rngTargetEnd = ActiveWindow.RangeFromPoint(WordWindowLeft, WordWindowTop + windowUsableHeight)
rngTargetStart.End = rngTargetEnd.End
If rngTargetStart.Tables.Count >= 1 Then
pageNumberTarget = rngTargetStart.Information(wdActiveEndPageNumber)
Set tbl = rngTargetStart.Tables(1)
rngTargetStart.Start = ActiveDocument.Content.Start
Debug.Print "The table on page " & pageNumberTarget & " is number: " & rngTargetStart.Tables.Count
End If
End Sub

Return the top ten Google results hyperlinks

I am looking to adapt the code presented by Santosh so that I can return the top ten links, not just the top one result, of a particular Google search. I need to upload > 1000 search queries and map the results against expected results but I care about more than just the #1 result, I'm looking to see if it returns within the top ten. I looked into the html and VBA and I can't figure it out.
Using VBA in Excel to Google Search in IE and return the hyperlink of the first result
Without testing, it looks like this line here is pulling one element in a collection:
Set link = objH3.getelementsbytagname("a")(0)
So, you're setting the "Link" object to the first object (object 0) in the objH3 collection that has the "a" tag.
What you want to do is loop through that collection. Ex:
Set links = objH3.getelementsbytagname("a")
For i = 0 To 9
set link = links(i)
'do stuff
next
Edit:
The loop should be on a higher level of objects:
For i = 0 To 5
Set objH3 = objResultDiv.getelementsbytagname("H3")(i)
Set link = objH3.getelementsbytagname("a")(0)
' Do Stuff
Next I
Thank you, Richard, for the update.

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

GetCrossReferenceItems in msword and VBA showing only limited content

I want to make a special list of figures with use of VBA and here I am using the function
myFigures = ActiveDocument.GetCrossReferenceItems(Referencetype:="Figure")
In my word document there are 20 figures, but myFigures only contains the first 10 figures (see my code below.).
I search the internet and found that others had the same problem, but I have not found any solutions.
My word is 2003 version
Please help me ....
Sub List()
Dim i As Long
Dim LowerValFig, UpperValFig As Integer
Dim myTables, myFigures as Variant
If ActiveDocument.Bookmarks.Count >= 1 Then
myFigures = ActiveDocument.GetCrossReferenceItems(Referencetype:="Figure")
' Test size...
LowerValFig = LBound(myFigures) 'Get the lower boundry number.
UpperValFig = UBound(myFigures) 'Get the upper boundry number
' Do something ....
For i = LBound(myFigures) To UBound(myFigures) ‘ should be 1…20, but is onlu 1…10
'Do something ....
Next i
End If
MsgBox ("Done ....")
End Sub*
Definitely something flaky with that. If I run the following code on a document that contains 32 Figure captions, the message boxes both display 32. However, if I uncomment the For Next loop, they only display 12 and the iteration ceases after the 12th item.
Dim i As Long
Dim myFigures As Variant
myFigures = ActiveDocument.GetCrossReferenceItems("Figure")
MsgBox myFigures(UBound(myFigures))
MsgBox UBound(myFigures)
'For i = 1 To UBound(myFigures)
' MsgBox myFigures(i)
'Next i
I had the same problem with my custom cross-refference dialog and solved it by invoking the dialog after each command ActiveDocument.GetCrossReferenceItems(YourCaptionName).
So you type:
varRefItemsFigure1 = ActiveDocument.GetCrossReferenceItems(g_strCaptionLabelFigure1)
For k = 1 To UBound(varRefItemsFigure1)
frmBwtRefDialog.ListBoxFigures.AddItem varRefItemsFigure1(k)
Next
and then:
frmBwtRefDialog.Show vbModeless
Thus the dialog invoked several times instead of one, but it works fast and don't do any trouble. I used this for one year and didn't see any errors.
Enjoy!
Frankly I feel bad about calling this an "answer", but here's what I did in the same situation. It would appear that entering the debugger and stepping through the GetCrossReferenceItems always returns the correct value. Inspired by this I tried various ways of giving control back to Word (DoEvents; running next segment using Application.OnTime) but to no avail. Eventually the only thing I found that worked was to invoke the debugger between assignments, so I have:
availRefs =
ActiveDocument.GetCrossReferenceItems(wdRefTypeNumberedItem):Stop
availTables =
ActiveDocument.GetCrossReferenceItems(wdCaptionTable):Stop
availFigures = ActiveDocument.GetCrossReferenceItems(wdCaptionFigure)
It's not pretty but, as I'm the only person who'll be running this, it kind of works for my purposes.