SharePoint Development from a Documentation Perspective
(Note: This is the third in a series of entries that aim to introduce experienced programmers to the Publisher object model.
The first entry covered creating Web pages programmatically. You can read it here.
The second entry covered working with wizards and templates. You can read it here.)
While the way you work with text in the Publisher object model is very similar to how you work with text in other word processing applications, such as Word, in does differ in one very important aspect: because Publisher is a desktop design and publishing application, it provides you to ability to include multiple text flows in a single publication, and programmatically control how those flows are laid out and formatted.
For example, suppose you needed to create a newsletter. You would probably want to include several different articles, each with their own distinct content and formatting. In addition, a given article might continue from one page to another; those pages might not be contiguous. The final result might look something like the three-page newsletter in Figure 1.
Figure 1. Lay out of a sample newsletter.
In Publisher, each distinct flow of text is referred to as a story. A given story may span one or more textboxes, on one or more pages. Those pages need not be contiguous. Consider the sample newsletter in Figure 1. The first story is contained in a single text box on the first page. The second story starts in a text box on page one, then concludes in another textbox on page three. Story three is contained in three text boxes: two on page two, and a single text box on page three.
Publisher lets you link multiple text boxes together to contain a story, and automatically manages how text flows from one text box to the next. If there isn't room to display all the story text in the first text box, the text flows into the second, and so on. If there's too much text to display in the final text box, Publisher stores the remaining text in an overflow buffer. Publisher automatically adjusts the amount of text contained in each text box as you resize the textboxes, format the text, etc.
When working with story text through the Publisher object model, it is important to distinguish between the story itself, and the individual text boxes that contain it. Each story in a given publication is represented by a Story object contained in the Document.Stories collection. Operations performed on a Story object affect the entire story, regardless of the textboxes in which it is contained. For example, the following code sets the font size for a story to 12 points, for all the text boxes that contain that story.
The following example, on the other hand, set the font size for only the story text contained in the specific textbox:
You can not create or delete stories through the Stories collection. Rather, when you add a textbox to the publication, you are also adding a new story to the publication. For example, the following code adds a textbox, and therefore a story, to the active publication, even though no text has been specified for the text box.
Left:=72, Top:=72, Width:=200, Height:=200
If you then deleted the text box, or linked it to an existing text box, the number of stories in the publication would decrease by one. Use the Stories.Count property to return the number of stories in a publication at a given time.
To delete a story, you must delete each text box that contains the story text.
Use the TextFrame property to access the text frame of the first text box of a story. Use the TextRange property to return the full text of the story.
A story may also be placed within a table, in which case the story would be contained in one or more Cell objects rather than TextFrame objects. In such a case, use the Cell.TextRange property to access the text in a specific table cell. Use the HasTextFrame property to determine if a story is contained in one or more TextFrame objects.
The only objects to which you can link a text box are:
· An empty text box that is not already part of a chain of connected text boxes.
· A drawing shape that has a text frame. To determine if a shape has a text frame, use the Shape.HasTextFrame property.
Each text box contains a TextFrame object, which contains the text in shape, as well as the properties that control the margins and orientation of the text frame. The TextFrame object includes several members that enable you to determine if a text box is part of a linked story, and to set or sever the connections between text boxes.
To determine if a text box is connected to a preceding or following text box, use the HasPreviousLink and HasNextLink properties, respectively. To access the text frames of those connected shapes, use the PreviousLinkedTextFrame and NextLinkedTextFrame properties. To connect one text box to the text box you want to follow it, use the NextLinkedTextFrame property as well.
To break the forward link for a specified text frame, use the BreakForwardLink method. Applying this method to a shape in the middle of a chain of shapes with linked text frames will break the chain, leaving two sets of linked shapes. All of the text, however, will remain in the first series of linked shapes.
The following example illustrates the relationship between a story and the connected text boxes that contain it. The example adds three text boxes to the active publication, and adds text to the first text box. At this point, with the text boxes not connected, three stories have been added to the publication's Stories collection. Next, the example links the three text boxes together by setting the NextLinkedTextFrame property of the first two text boxes. By doing this, two stories have been removed from the Stories collection. Note that the code calls the ValidLinkTarget method to determine if each text frame is a legitimate target to which to link.
Finally, the third text box is disconnected by calling the BreakForwardLink method for the second text box. The story text is now stored only in the first two text boxes, and the overflow buffer if necessary. In addition, text box three now represents a new, empty story.
Dim tb1 As Shape
Dim tb2 As Shape
Dim tb3 As Shape
Set tb1 = ActiveDocument.Pages(1).Shapes.AddTextbox _
Left:=72, Top:=36, Width:=72, Height:=36)
tb1.TextFrame.TextRange = "This is some text. " _
& "This is some more text. This is even more text. " _
& "And this is some more text and even more text."
Set tb2 = ActiveDocument.Pages(1).Shapes.AddTextbox _
Left:=72, Top:=108, Width:=72, Height:=36)
Set tb3 = ActiveDocument.Pages(1).Shapes.AddTextbox _
Left:=72, Top:=180, Width:=72, Height:=36)
If tb1.TextFrame.ValidLinkTarget(tb2) Then
tb1.TextFrame.NextLinkedTextFrame = tb2.TextFrame
If tb2.TextFrame.ValidLinkTarget(tb3) Then
tb2.TextFrame.NextLinkedTextFrame = tb3.TextFrame
MsgBox "There are currently " & _
ActiveDocument.Stories.Count & _
" stories in this publication.", , "Story Count"
There is no object in the Publisher object model that represents the overflow buffer for a specific story. Each TextFrame has an Overflowing property, however, that indicates whether text is overflowing from that text box into the overflow buffer. For linked text frames, only the final text frame can be overflowing.
For a given story, the text in the overflow buffer is the difference between the End property of the final linked TextFrame in the story, and the End property of the Story object itself.
For example, the following procedures retrieves the text in the overflow buffer. First, the code determines if the selected text box has overflow text. If it does, the procedure retrieves the text contained in the overflow buffer by using the Characters method. This method returns a TextRange object that contains the characters from the end of the text box TextRange object to the end of the Story object. The code then uses the Text property of the resulting TextRange object to return a string representing the overflow text, which it then displays in a message box.
Dim ot As String
If .Overflowing Then
ot = .Characters(.End, .Story.TextRange.End).Text
MsgBox prompt:=ot, Title:="Overflow Text"
Also, each TextFrame object also has an AutoFitText property, which lets you set how you want Publisher to deal with overflowing text:
· Allow the text to overflow
· Reduce the text size so that the it fits in the text frame
· Reduce or enlarge the text size so it fills the text frame
The following text boxes cannot be part of a chain of connected text boxes: headers or footers, navigation bars, inline objects, personal information text boxes, text boxes already containing text, or text boxes set to automatically reduce text size.
Finally, each TextFrame and TextRange object has a Story property, which enables you to access the Story object associated with a specific text frame or text range.
Figure 2 illustrates the structure of the Publisher object model surrounding the Story object. It also includes the TextFrame properties concerned with manipulating the text frames of a specific story.
Figure 2. The Story Object Model Structure
Note: This is the second in a series of entries that aim to introduce experienced programmers to the Publisher object model. The first entry covered creating Web pages programmatically. You can read it here.
Publisher has a different concept of templates and wizards than other Office programs, such as Word. In Publisher, both terms refer to publication types on which you can base your publications, with important differences:
· Publication wizards are pre-defined publications that come bundled with Publisher. These publication wizards contain text boxes and other design elements that you can customize, and to which you can add your content, in the publications you create using them. Publication wizards also contain design automation options that enable you to quickly change the lay out and design of your publication.
· Templates are user-created publications that you can save to use as the basis for creating other publications. If you save the template to a specific location, Publisher then makes it available as a template on the New Publication task pane in the application interface.
Let’s examine each of these in detail.
Publication wizards are one of the most powerful and versatile features in Microsoft® Office Publisher 2003. As mentioned above, wizards are pre-defined publication templates included as part of Publisher. Wizards enable you to quickly generate professional-looking publications in a wide range of formats, from invitations to flyers to catalogs to websites.
When you create a publication using a wizard, Publisher populates the new publication with design elements based on the wizard type and design you choose. You can add your content to these elements, as well as customize the elements as you desire. You can change the appearance of the publication later by simply specifying a different design available for that wizard. The wizard then morphs the publication to adhere to that design scheme.
In Publisher, morphing refers to an object’s ability (be it a shape, page, or entire publication) to change its appearance based on the user’s choice of design. Choose a different design, and Publisher automatically updates the object according to the design chosen.
Some wizards generate multi-page publications, with different content and design options depending on the page. For example, a newsletter may include different design elements present on the front cover than on the interior pages.
Unlike templates in applications such as Word, you cannot access or alter publication wizards themselves. However, you can create a user-defined template based on a publication wizard. In such a case, you can alter the template in any way you like, such as adding VBA code to the publication, and the template retains the morphing and other functionality of the wizard on which it is based. For more information, see “Working with Templates.”
The Publisher object model lets you extend the design flexibility in publication wizards even farther by automating the generation and customization of publications based on wizards. Any publication created based on a publication wizard template has a Wizard object as the child of its Document object. If you create a publication based on a multi-page wizard template, the individual pages in the publication may have wizard properties that apply to only that page. In such a case, each Page object in the publication contains its own Wizard object as well. For more information on programmatically working with publication wizards, see Creating and Customizing Wizard Publications in Publisher 2003.
But not only do publications and pages have wizard properties, but shapes do as well. Any shape that Publisher adds to the default appearance of a publication based on a wizard design has properties that uniquely identify it. Publisher uses these properties to keep track of shapes that ‘belong’ to the wizard design, as opposed to any custom shapes the user might add to the publication later. For more information, see Identifying wizard shapes in a publication.
Note Publisher also includes group wizard shapes, which are related to, but independent from, the publication and page-level wizards. Group wizard shapes are pre-defined group shapes, such as calendars, coupons or Web navigation bars, that contain design automation options. Unlike wizard publications, you can add group wizard shapes to any publication, whether or not it’s based on a publication wizard. Also, You set the design of each group wizard shape individually. For more information, see Working with group wizard shapes.
You can also create templates in Publisher. Templates are especially useful if you create certain publications, such as newsletters, flyers, or postcards, over and over again. Templates enable you to design master publications that reflect your company’s brand and identity; you can then use that template to create new publications, adding only the information that is unique to each publication.
Publisher templates are simply publications saved to a specific user directory. Each time you launch a new instance of Publisher, the application makes the publications in that directory available as template on the New Publication task pane.
To create a template, save your publication to the following user directory:
Drive:\Documents and Settings\userName\Application Data\Microsoft\Templates
Where Drive represents the computer drive letter, and userName represents the name of the user to whom you want to make this publication available as a template. If you want to make the template available to multiple users on the same computer, you must save the template to the above location in each user’s directory.
Publisher displays available templates under Templates in the New Publications task pane. Because Publisher loads the available templates on launch, you need to open a new instance of Publisher to for the new template to be available. Even then, the new template is not displayed in any instances of Publisher launched before the template was saved.
If you do not have any templates saved, the Templates folder does not appear on the New Publication task pane.
To save a publication as a template programmatically, use the Document.SaveAs method, specifying the file location above in the Filename parameter. This is directly equivalent to selecting Save as type: Publisher Template in the Save As dialog box.
The following function saves the specified document as a template:
Function SaveAsTemplate(pub As Document, fileName As String)
.SaveAs fileName:= _
"C:\Documents and Settings\user\Application Data\Microsoft\Templates\" _
& fileName, _
You can specify custom categories to group your templates. The custom categories are listed under Templates in the New Publications task pane. To do this, specify a category for the publication before you save it as a template. From the File menu, select Properties. On the Summary tab, enter a value for Category. If you do not specify a category for your template, Publisher displays it in a category named My Templates by default.
Note There is no way to programmatically set a publication's properties, such as Category, using the Publisher object model.
Any Visual Basic for Applications (VBA) code contained in the template gets copied into any publications you later create based on the template.
If you create a template based on a publication wizard, any publications you create based on the template retain the design automation functionality of the wizard.
Publications based on a template retain no link to that template. Any changes you later make in the template are not propagated to any publications previously created from that template.
Programmatically, work with templates as you would with any other Publisher files. There are no object model objects or properties specific to templates. For example, you cannot create a publication based on a template using the NewDocument method. In such a case, use the Document.Open method to open the template file directly, and then use the Document.SaveAs method to create a new publication based on the template.
As mentioned above, you cannot edit any of the publication wizards included in Publisher. You can, however, create a template based on a publication wizard. This template could include any code you wanted to make available for publications based on that publication wizard.
However, because users employ a number of publication wizards to create a wide range of publication types, in many cases it’s not practical to make your macro code available through creating templates. If you had particular macro functionality you wanted to make available for all publication types, you would have to create a separate template with that code for each publication wizard. In such cases, it’s best to just create an add-in for Publisher and deploy your code that way.
I got an email the other day from a user who asked if the NewDocument event in Publisher actually worked. It does, but it is a little more complicated than you'd think. Read on.
To enable event handling within a typical application, you create a new class module, and declare an Application object with events:
Public WithEvents App As Publisher.Application
Then, write your event handlers:
Private Sub App_NewDocument(ByVal Doc As Document)
MsgBox "You've created a new publication", , "New Pub Created"
Finally, initialize the your object with the Application object:
Dim pub As New Class1
Set pub.App = Publisher.Application
Which is exactly what the user was doing. But whenever he created a new document, either through the user interface or programmatically, his NewDocument event handler was never invoked. The NewDocument event didn't seem to be firing.
Actually, it was; he just hadn't hooked up his event handler to the right Publisher.Application object.
Publisher is a single document interface (SDI) application; each document you have open resides in a separate instance of Publisher. Which means if you have an instance of Publisher that already contains an open document, and you open another document, you're actually launching another instance of Publisher first. That second instance of Publisher then opens the document, even though it looks like you're launching the document directly from the first instance.
That's why his NewDocument event handler never fired. His code was watching the first instance of Publisher; but with a document already open in the first instance, any commands to create a new document actually launched another instance of Publisher, which is where the NewDocument event was raised.
The same is true of the DocumentOpen event; if the instance of Publisher already contains an open document, then an additional instance launches, and the DocumentOpen event is raised in that second instance.
The only time either event would occur in an existing instance of Publisher is if Publisher was open, but didn't have a document open. Which means you can't write VBA code in a document to have it watch it's own application instance for those events. By simple fact that the document containing the VBA code was open, additional instances of Publisher would be launched for any open or new document commands.
You can, of course, use the DocumentOpen and NewDocument events from:
· Another instance of Publisher
· A Publisher add-in
· Another application
So let's create an example for the first bullet point, another instance of Publisher. The example below creates a new instance of Publisher, and initializes code to respond to events raised within that instance.
First, create a new class module, and declare an Application object with events, as you would normally:
Then, write handler procedures for all the application events to which you want to respond:
Private Sub App_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean)
MsgBox "You are about to close: " & Doc.Name
Private Sub App_DocumentOpen(ByVal Doc As Document)
MsgBox "You have opened: " & Doc.Name, , "Document Open"
Private Sub App_WindowPageChange(ByVal Vw As View)
MsgBox "Hey", , "Window Page Change Event"
However, in the procedure that initializes your application object, set your variable to the application instance launched when Publisher opens a new document, not the application instance that contains the document with the VBA project.
For example, the following code creates a new document, and initializes the pub.App variable with the resulting new instance of Publisher. The event handlers now fire when events are raised in that application instance. Calling the NewDocument method, as the next line of code does, actually results in two events: the DocumentBeforeClose event for the new document open in the previous line, and then the NewDocument event for the new document that replaces it.
Set pub.App = NewDocument.Application
You could also create a new instance of Publisher using the New VB keyword. However, instances of Publisher launched this way are not visible by default; this enables you to launch Publisher and automate it without displaying it to the user. So you have to explicitly specify the application be visible, like so:
Set pub.App = New Publisher.Application
pub.App.ActiveWindow.Visible = True
Now you event handler can respond to events the user raises in that Publisher instance, including DocumentOpen and NewDocument events. The event handlers I wrote simply pop up message boxes, but you get the idea. Just remember, those message boxes will appear in the Publisher instance running the code, not the Publisher instance actually raising the events.
One last piece of house-keeping. We want to release the pub.App variable if and when the user closes that instance of Publisher. So let's create a procedure that does that, and place it in the ThisDocument project:
Public Sub FreeApp()
Set pub.App = Nothing
And then call that procedure from the Quit event handler:
Private Sub App_Quit()
On a related note, did you know it's impossible to programmatically arrive at a Publisher instance that doesn't have a document open? The New keyword launches a new instance of Publisher, with a new blank publication open. The Document.Close method closes the current publication, but then opens a new blank publication in it's place. There is no way to launch Publisher without also opening a publication. Likewise, there is no way to programmatically close the current publication without either also closing Publisher, or getting a new publication in its place. To get an instance of Publisher without an open publication, you have to go through the user interface.