PDA

View Full Version : Headers and Footers



JimHelton
01-16-2012, 12:04 PM
Hello! I found this forum in my quest for VBA knowledge and it looks like an excellent resource. I'm a bit new to VBA and have run into a snag with an existing macro used by our company.

The goal of the macro is to place an image on the first page (making it essentially a coverpage) and then to put an image and numbering on the remaining pages.

The original macro worked great on a new document that did not contain sections, etc. However, it would do some crazy things on documents that had multiple sections and linked headers/footers. I've added a section to strip all linking and that fixed the issue with only having the coverpage image on the first page, but now it dumps multiple instances of the footer on the first page and on one page later in the doc (page 9). My goal ultimately is to have the "GSI Letterhead 1st Page" building block on only the first page and the "-GSI Icon Footer" building block in the footer of all other pages.

What am I doing wrong? Can anyone provide an example of how this might be accomplished by extending this code or even starting clean?

Thanks!

Public Sub GSIAddElectronicLetterhead()
' Strip all linking from all sections
Dim mySection As Section, myHF As HeaderFooter

For Each mySection In ActiveDocument.Sections()
For Each myHF In mySection.Headers
myHF.LinkToPrevious = False
Next

For Each myHF In mySection.Footers
myHF.LinkToPrevious = False
Next
Next

' Add coverpage by using first page header.
Dim pageCount As Long, Count As Long

For Each myTemplate In Templates
If myTemplate.Name = "gsi.dotm" Then
ActiveDocument.Range.GoTo(wdGoToPage, wdGoToAbsolute, , "1").Select
ActiveDocument.PageSetup.DifferentFirstPageHeaderFooter = True
ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
myTemplate.BuildingBlockEntries("GSI Letterhead 1st page").Insert Where:=Selection.Range, RichText:=True
ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument

' Place footer on all pages except first.
Count = 1
For Each mySection In ActiveDocument.Sections()
For Each myHF In mySection.Footers
If Count <> 1 Then
WordBasic.ViewFooterOnly
myTemplate.BuildingBlockEntries("-GSI Icon Footer").Insert Where:=Selection.Range, RichText:=True
WordBasic.GoToHeader
Selection.Delete Unit:=wdCharacter, Count:=1
ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument
End If
Next
Count = Count + 1
Next
End If
Next

End Sub

macropod
01-16-2012, 03:59 PM
Hi Jim,

Perhaps it's just me, but I can't see why you wouldn't simply create the document with the 'gsi.dotm' template as its base. Provided that template is appropriately configured, there should be no need for any of your code.

If you're re-puposing an existing document, you should be able to use something like:
Sub GSIAddElectronicLetterhead()
Dim Scn As Section, HdFt As HeaderFooter
With ActiveDocument
' Ensure the "gsi.dotm" template is attached, exit if not found
On Error GoTo ErrExit
.AttachedTemplate = "gsi.dotm"
If .Sections.Count > 1 Then
For Each Scn In .Sections
With Scn
' Delete all 'Different First Page' & Different Odd And Even' Headers & Footers
With .PageSetup
.DifferentFirstPageHeaderFooter = False
.OddAndEvenPagesHeaderFooter = False
End With
' Link all headers & footers
.Headers(wdHeaderFooterPrimary).LinkToPrevious = True
.Footers(wdHeaderFooterPrimary).LinkToPrevious = True
End With
Next
' Unlink the Section 2 header
.Sections(2).Headers(wdHeaderFooterFirstPage).LinkToPrevious = False
End If
With .Sections(1)
' Add coverpage by using first page header.
.PageSetup.DifferentFirstPageHeaderFooter = True
Templates(ActiveDocument.AttachedTemplate).BuildingBlockEntries("GSI Letterhead 1st page").Insert _
Where:=.Headers(wdHeaderFooterFirstPage).Range, RichText:=True
Templates(ActiveDocument.AttachedTemplate).BuildingBlockEntries("-GSI Icon Footer").Insert _
Where:=.Headers(wdHeaderFooterPrimary).Range, RichText:=True
End With
' Replicate the 2nd page header in Section 2
If .Sections.Count > 1 Then
With .Sections(2)
.Headers(wdHeaderFooterFirstPage).Range = .Sections(1).Headers(wdHeaderFooterPrimary).Range
.Range.Characters.Last.Delete
End With
End If
End With
ErrExit:
End Sub

JimHelton
01-17-2012, 07:23 AM
Thank you for the reply Paul! I am indeed attempting to add the formatting to existing documents with various source templates.

When I attempted to test your macro I get an error:

Compile error:
Method or data member not found

On this line:

With .Sections(2)
.Headers(wdHeaderFooterFirstPage).Range = .Sections(1).Headers(wdHeaderFooterPrimary).Range
.Range.Characters.Last.Delete
End With

Any ideas?

macropod
01-17-2012, 02:57 PM
Hi Jim,

I must admit I hadn't tested the code, and I'm not sure that part's actually needed. What happens if you omit it?

Alternatively, change:
.Sections(1).Headers(wdHeaderFooterPrimary).Range
to:
ActiveDocument.Sections(1).Headers(wdHeaderFooterPrimary).Range

JimHelton
01-19-2012, 03:37 PM
Well, I am making some progress thanks to your version, but have made a couple minor changes to get it working. However, now it's doing something odd in the footer and I have to figure out how the document is formatted to cause it and then I'll add tweaking that to macro as well.

Here is where the macro is currently:
Sub GSIAddElectronicLetterhead()
Dim Scn As Section, HdFt As HeaderFooter

For Each myTemplate In Templates
If myTemplate.Name = "gsi.dotm" Then

With ActiveDocument
If .Sections.Count > 1 Then
For Each Scn In .Sections
With Scn
' Delete all 'Different First Page' & Different Odd And Even' Headers & Footers
With .PageSetup
.DifferentFirstPageHeaderFooter = False
.OddAndEvenPagesHeaderFooter = False
End With
' Link all headers & footers
.Headers(wdHeaderFooterPrimary).LinkToPrevious = True
.Footers(wdHeaderFooterPrimary).LinkToPrevious = True
End With
Next
' Unlink the Section 2 header
.Sections(2).Headers(wdHeaderFooterFirstPage).LinkToPrevious = False
End If
With .Sections(1)
' Add coverpage by using first page header.
.PageSetup.DifferentFirstPageHeaderFooter = True
myTemplate.BuildingBlockEntries("GSI Letterhead 1st page").Insert _
Where:=.Headers(wdHeaderFooterFirstPage).Range, RichText:=True
myTemplate.BuildingBlockEntries("-GSI Icon Footer").Insert _
Where:=.Footers(wdHeaderFooterPrimary).Range, RichText:=True
End With
End With
End If
Next
End Sub

It wasn't finding the template so I removed that logic and used the method of the original macro to get the template accessible.

I'm almost there thanks to your help!

Frosty
01-19-2012, 04:11 PM
Jim,

The structure for your routine is a little wonky... since stepping through it is going to be a bit of a headache.

You're looping through your templates collection in order to locate the proper Template for use with your building block insertion into the active document. You could just as easily skip the For loop and use the Templates collection directly:


Set myTemplate = Templates("gsi.dotm")


It also looks like you are not utilizing Option Explicit at the top of your code module. You should put that in-- it helps troubleshooting immensely (you will quickly see that you need to declare your myTemplate variable with a Dim myTemplate As Template statement.

macropod
01-20-2012, 03:33 AM
Hi Frosty,

You'll note that, like your suggestion, the code I supplied avoided the templates loop, via:
.AttachedTemplate = "gsi.dotm"
The only reason I can think of for that not working is that the full path might have been needed.

As for Jim's "now it's doing something odd in the footer", that doesn't give anyone anything useful to work with in finding a solution.

Frosty
01-20-2012, 08:08 AM
Hi Paul,

It depends on where the Building Block Entry is stored... I'm guessing .AttachedTemplate failed because the autotext entry is not actually in the ActiveDocument.AttachedTemplate... but rather stored in a global addin (which is in the Templates collection, but wouldn't be the AttachedTemplate). I don't think full path would be the issue there... but there are a lot of ways to set this up.

Frosty
01-20-2012, 08:33 AM
Incidentally, not to step on toes... but I think the issue in Jim's code above has to do with having lost Paul's original methodology. Since there are many ways to do this, I don't want to confuse the progress being made.

But you're using a for each loop to "stabilize" all header/footer objects in the document, then making sure the first page is different for the first section... but you didn't follow through and make sure that the 2nd section gets the right info.

At the end of the day, Paul's original code would have done the following:
1. Linked all headers/footers
2. Turned on FirstPageDifferent in the first section only
3. Inserted an autotext entry into the header of the FirstPageHeader (Section 1)
4. Inserted an autotext entry in the footer of the PrimaryFooter (Section 1)
5. Copied the primary footer of Section 1 to Section 2... if Section 2 existed. Since all footers were linked in Step 1... this would be sufficient to display the footer in all subsequent footers (especially with first page different turned off in all sections beyond Section 1).

This methodology, however, could cause issues if you were trying to preserve other footer information in a multi-section document (i.e., if you want to leave footer text alone, but want to make sure a footer logo shows up on every footer).

There are so many ways to do what you're doing... it heavily depends on what you want available to you in the most complicated scenario. Do you want the ability to have a multi-section document, with some sections having primary/first page headers/footers, preserving any existing text, but simply adding/replacing some logos in specific locations?

If so-- you're going to need a different methodology than the above.

But, if you don't care about any information in your headers/footers apart from the autotext/building block entries you want to insert at very specific locations... you're on the right track.

JimHelton
01-20-2012, 09:18 AM
In Paul's original code there was a check for the template which failed (meaning the macro did nothing). I removed the check and it errored on finding the Building Blocks, so I put in the full path to the GSI.dotm file and it still failed. That is why I went back to the method that had worked in the past.

Being new to VBA, and even advanded formatting in Word, I don't fully understand the model and relationships. But when something works I can usually figure out how to move that functionality between programs and that is what I did. It does seem to me that you should be able to explicitly reference it, but I'm not sure yet why that is not working.

Regarding why I am trying to do it this way, it's because I have no control over the original formatting of the document. The original macro I posted works great on a new document, but failed miserably on documents with various sections, header/footer relationships and such. My request is to strip all that as it relates to the header/footer and put there what I want.

I am using an internal document to test this on-going work. In its current state the macro properly updates the first page, but on the first few pages the footer is not properly formated, then a few pages later it loses the image that is part of the footer but everything else is in the right place within the footer. I will see if I can take some screenshots and post so that you guys can see what I mean, but figured if I could find out what it was about the document that was different between pages that would be more helpful.

Frosty
01-20-2012, 10:36 AM
Conceptually, there is a difference between an AttachedTemplate and a Global Addin.

A global addin is something which is loaded into the application environment and persists (sticks around) until it is unloaded. Most commonly, global addins are loaded when word starts, and unloaded when you exit word.

An attached template is only immediately available to documents based on that template... typical example would be something like a letter document which is based on a letter template.

Code which relies on the attached template of the active document will fail if, obviously, the attached template is not what you want.

The templates collection is a dynamic collection of the "available" templates. But "available" is a funny thing. The Letter template will be included in the Templates collection (along with any global addins which are also word templates) if you have *any* Letter documents open (i.e., documents based on the letter template, with the attachedtemplate property of that document = to the Letter template name).

Why am I explaining all this? Because it's tough to give you the real "right" answer of how to proceed without knowing how your stuff is set up. Word 2007/2010 complicates this a bit, by automatically loading some additional global addins like the building blocks stuff, but not having them available in the Templates collection, but you can still insert autotext/building blocks from them without explicitly referencing them.

Anyway... long story short... where is your gsi.dotm located? Is that where your code is too? It's obviously where you keep your autotext entries... but is this also a document which all of your documents are based on?

Answers to those questions will go a long way towards sorting out what will work for you in a variety of scenarios.

Also, conceptually... as far as headers/footers go... it's important to understand that those locations exist regardless of whether your are showing them or not. A document with a single section has 3 headers and 3 footers.
Primary/Odd Header
First Page Header
Even page Header
Primary/Odd Footer
First Page Footer
Even page Footer

When you set .DifferentFirstPageHeaderFooter = False... all you're doing is "hiding" the First Page Header and Footer items, but they still exist.
When you set .OddAndEvenpagesHeaderFooter = False, same thing.

When you combine that with .LinkToPrevious being true, you can get some pretty whacky things such as...

Section 1 (Different First Page on)
Sections 2-8 (No Different First page on, but code which linked all header/footer objects on, but manually having unlinked the primary header/footer)
Section 9 (Different First Page on -- suddenly displaying the same first page header info as Section 1)

So you can set up situations where hidden headers and footers are actually linked, and thus are "passing" information several sections down the line.

JimHelton
01-20-2012, 11:11 AM
Very well explained. Based on this and some additional testing/tweaking it now looks like this:

Sub GSIAddElectronicLetterhead()
Dim Scn As Section, HdFt As HeaderFooter

For Each myTemplate In Templates
If myTemplate.Name = "gsi.dotm" Then

With ActiveDocument
If .Sections.Count > 1 Then
For Each Scn In .Sections
With Scn
' Delete all 'Different First Page' & Different Odd And Even' Headers & Footers
With .PageSetup
.DifferentFirstPageHeaderFooter = False
.OddAndEvenPagesHeaderFooter = False
End With
' Link all headers & footers
.Headers(wdHeaderFooterPrimary).LinkToPrevious = True
.Footers(wdHeaderFooterPrimary).LinkToPrevious = False
End With
Next
' Unlink the Section 2 header
.Sections(2).Headers(wdHeaderFooterFirstPage).LinkToPrevious = False
End If
With .Sections(1)
' Add coverpage by using first page header.
.PageSetup.DifferentFirstPageHeaderFooter = True
myTemplate.BuildingBlockEntries("GSI Letterhead 1st page").Insert _
Where:=.Headers(wdHeaderFooterFirstPage).Range, RichText:=True
End With
End With
End If
Next
End Sub

It now puts everything where it is supposed to be, but for some reason does not include the image that should be in the footer (based on the BuildingBlock).

To answer your questions about the template:

1. It is located in my profile in the \Application Data\Microsoft\Word\STARTUP folder.
2. The macro lives in this template.
3. Yes, the BuildingBlocks are in this template as well.

Frosty
01-20-2012, 12:05 PM
Jim,

If the gsi.dotm is in your profile word startup folder... then it is probably a global addin which is loaded when word starts. Since it is a .dotm, it is available to the Templates collection, but the "better" way of doing this (it's always a little sketchy to embed names in macros-- especially if you're going to deploy to your whole company) is to use ThisDocument.AttachedTemplate (i've demonstrated it's use in the code).

You also have a few more decisions to make, but mostly I think you should try using F8 to step through the code. If you have a wide enough monitor to watch the document and the code at the same time, it will help.

Viewing the locals window in the VBA window can also help... it will let you watch your variables as they get set. And you can then use the immediate pane to try rngInsert.Select and see what you're actually doing.

I suspect you have a few more decisions to make on how you want this code to run. But here is a slightly adjusted bit of code, for more easily separating out what is happening. I won't get into the "modularize your code" part of learning VBA, as that is for a later time.

Sub GSIAddElectronicLetterhead()
Dim Scn As Section
Dim HdFt As HeaderFooter
Dim myTemplate As Template
Dim rngInsert As Range
Dim oBB As BuildingBlock

'skip the For Each loop, and simply set it. it's the same thing
Set myTemplate = Templates("C:\my path\gsi.dotm")
MsgBox myTemplate.Name
'however, since it's in your profile, this wouldn't be deployable-- better to at least
'isolate the functionality in the following way
For Each myTemplate In Templates
If myTemplate.Name = "gsi.dotm" Then
'exiting the loop will preserve the myTemplate setting
Exit For
End If
Next
MsgBox myTemplate.Name

'but, since the code and autotext are in the same template, you could also use this,
'which is a bit simpler, since you don't have to embed the name of the template
'and this would be considered a "best practice", apart from having a separate function
'return the proper template
Set myTemplate = ThisDocument.AttachedTemplate
MsgBox myTemplate.Name

'changed the with structure, which is confusing to read
'sort out what to do with your document (not sure why to skip this on a single section document)
If ActiveDocument.Sections.Count > 1 Then
For Each Scn In ActiveDocument.Sections
With Scn
'page set up
.PageSetup.DifferentFirstPageHeaderFooter = False
.PageSetup.OddAndEvenPagesHeaderFooter = False
'headers (decide what to do about all of these
.Headers(wdHeaderFooterFirstPage).LinkToPrevious = True
.Headers(wdHeaderFooterPrimary).LinkToPrevious = True
.Headers(wdHeaderFooterEvenPages).LinkToPrevious = True
'footers
.Footers(wdHeaderFooterFirstPage).LinkToPrevious = False
.Footers(wdHeaderFooterPrimary).LinkToPrevious = False
.Footers(wdHeaderFooterEvenPages).LinkToPrevious = False
End With
Next
End If

'now... the work on the document -- section 1?
With ActiveDocument.Sections(1)
' Add coverpage by using first page header.
.PageSetup.DifferentFirstPageHeaderFooter = True

'get the building block
Set oBB = myTemplate.BuildingBlockEntries("GSI Letterhead 1st page")
'and set the range to insert
Set rngInsert = .Headers(wdHeaderFooterFirstPage).Range
'and insert it
oBB.Insert Where:=rngInsert, RichText:=True
End With

'just 2nd section? All other sections? All primary footers?
With ActiveDocument.Sections(2)
'get the building block
Set oBB = myTemplate.BuildingBlockEntries("-GSI Icon Footer")
'and set the range
Set rngInsert = .Footers(wdHeaderFooterPrimary).Range
oBB.Insert Where:=rngInsert, RichText:=True
End With

End Sub

JimHelton
01-23-2012, 07:56 AM
I think we might have it! Using your code I cleaned it up to what I needed and also put the footer addition into the first loop for each section after I realized I was essentially doing it twice. I then took your advice and stepped through the code and noticed that after setting the OddAndEvenPagesHeaderFooter to False it would insert a footer (must be from the origin template) and when my step to drop in the footer BuildingBLock ran it would stack the footers, which was the issue I saw last week. I dug around and found an example for deleting footers and added that into the code and BINGO!

I'm now going to start testing it on other documents which have exhibited issues and once confirmed will deploy it to my company.

Thank you guys for all of your help! Much easier to learn from something that works than puzzling out issues with bad code. For reference, here is the final code I am using:

Sub GSIAddElectronicLetterhead()
Dim Scn As Section
Dim HdFt As HeaderFooter
Dim myTemplate As Template
Dim rngInsert As Range
Dim oBB As BuildingBlock

'Setup object for template to be used later with BuildingBlock steps.
Set myTemplate = ThisDocument.AttachedTemplate

'Update header/footer linking throughout the document.
If ActiveDocument.Sections.Count > 1 Then
For Each Scn In ActiveDocument.Sections
With Scn
'page set up
.PageSetup.DifferentFirstPageHeaderFooter = False
.PageSetup.OddAndEvenPagesHeaderFooter = False
'headers (decide what to do about all of these
.Headers(wdHeaderFooterFirstPage).LinkToPrevious = False
.Headers(wdHeaderFooterPrimary).LinkToPrevious = False
.Headers(wdHeaderFooterEvenPages).LinkToPrevious = False
'footers
.Footers(wdHeaderFooterFirstPage).LinkToPrevious = False
.Footers(wdHeaderFooterPrimary).LinkToPrevious = False
.Footers(wdHeaderFooterEvenPages).LinkToPrevious = False

' Clear any existing footers
For Each HdFt In Scn.Footers
HdFt.Range.Delete
Next

'get the building block
Set oBB = myTemplate.BuildingBlockEntries("-GSI Icon Footer")
'and set the range
Set rngInsert = .Footers(wdHeaderFooterPrimary).Range
'and insert it
oBB.Insert Where:=rngInsert, RichText:=True
End With
Next
End If

'Add the header to the first page of the first section using the header.
With ActiveDocument.Sections(1)
' Add coverpage by using first page header.
.PageSetup.DifferentFirstPageHeaderFooter = True

'get the building block
Set oBB = myTemplate.BuildingBlockEntries("GSI Letterhead 1st page")
'and set the range to insert
Set rngInsert = .Headers(wdHeaderFooterFirstPage).Range
'and insert it
oBB.Insert Where:=rngInsert, RichText:=True
End With

End Sub

Frosty
01-23-2012, 10:42 AM
I think that's good improvement. Personally, I would reverse the order of operations, since I prefer to deal more sequentially (i.e., -- deal with my first section, and *then* deal with subsequent sections, if they exist). I know that can screw things up a bit with the .LinkToPrevious (inserting stuff in section 1 and then turning off .LinkToPrevious in section 2), but I prefer to work through those issues, because if I don't think about them, they'll make me think about them later.

It makes more sense to me to develop in that way-- but that's the kind of your-flavor-my-flavor things which reasonable people will disagree on.

My bigger concern with this kind of code, is that it is a really strict set of requirements... so you can only safely run it on certain kinds of documents. There is no checksum for any "bad" behavior (i.e., if you have a 2 section document, where the second section a multi page landscape table with some very specific information in the first page footer, and some very specific *different* information in the primary footer of that section-- this code is going to delete that information).

Also, again, I'm not sure how you want your documents set up... but this is what you end up with, in 2 basic scenarios:

Scenario #1 -- 1 section, 2 pages, blank document (no existing footers).
Page 1 -- your first page header
Page 2 -- blank footer

Scenario #2 -- 2 section, 2 pages each, (no existing footers)
Page 1 -- your first page header
Page 2 -- blank footer
Page 3 -- your footer
Page 4 -- your footer

Is that what you want? Because while you define the 1st section to have different first page... you don't insert the footer. That might mean your first section is always 1 page, so you never see the primary footer of section 1. But it also begs the question-- why have first page different at all?

Glad it's working... just wanted to add a few extra thoughts.

Also, since your new "delete existing footers" code is within the With Scn code, you don't need to reference Scn.Footers

Here's another "flavor" of your code, which does the same thing... but puts the .Index test (skipping the first section) within your For...Loop. It *kind* of bothers me, structurally, that you skip the whole loop on single section documents, but then don't skip processing the first section on multi-section documents. That's the kind of quirk that comes back to haunt you later, when you're troubleshooting.

Sub GSIAddElectronicLetterhead()
Dim Scn As Section
Dim HdFt As HeaderFooter
Dim myTemplate As Template
Dim rngInsert As Range
Dim oBB As BuildingBlock

'Setup object for template to be used later with BuildingBlock steps.
Set myTemplate = ThisDocument.AttachedTemplate

'get the building block (don't need to set for each section)
Set oBB = myTemplate.BuildingBlockEntries("-GSI Icon Footer")

'loop through all sections, ignoring the first section
For Each Scn In ActiveDocument.Sections
With Scn
'skip the first section
If .Index > 1 Then
'page set up for the section
.PageSetup.DifferentFirstPageHeaderFooter = False
.PageSetup.OddAndEvenPagesHeaderFooter = False

'turn off link to previous in all headers
For Each HdFt In .Headers
HdFt.LinkToPrevious = False
Next

'and in all footers-- and also delete the content of each footer
For Each HdFt In .Footers
HdFt.LinkToPrevious = False
HdFt.Range.Delete
Next

'set the insert range
Set rngInsert = .Footers(wdHeaderFooterPrimary).Range
'and insert the building block
oBB.Insert Where:=rngInsert, RichText:=True
End If
End With
Next

'Add the header to the first page of the first section using the header.
With ActiveDocument.Sections(1)
' Add coverpage by using first page header.
.PageSetup.DifferentFirstPageHeaderFooter = True

'get the building block
Set oBB = myTemplate.BuildingBlockEntries("GSI Letterhead 1st page")
'and set the range to insert
Set rngInsert = .Headers(wdHeaderFooterFirstPage).Range
'and insert it
oBB.Insert Where:=rngInsert, RichText:=True
End With

End Sub

You could, obviously, also put everything in the For Each loop, and simply have a If .Index = 1 Then... [do section 1 stuff] Else...[do all other section stuff] End If. But these are all flavors. It ultimately comes down to what feels the most readable for you-- and then just make sure you comment your code a lot, so you don't have to remember what it was doing in 6 months when you come back to it.