SharePoint Development from a Documentation Perspective
Today I'm going to talk about the Tags collection, a feature in the Publisher object model that hasn't gotten a lot of notice, but is extremely useful when you examine it. (And I'm as guilty as anyone of ignoring it; one of the Publisher testers pointed out tags to me months ago, and it's taken this long for me to start playing around with them.)
Tags are, in essence, generic holding bins where you can store whatever information you want to persist with the publication. The Tags collection is a child of the Document, Page, and Shape objects, so you can save data at the publication, page, or individual shape level.
Each Tag object consists of two properties: a name, and a value. The Name property is simply a string that identifies the tag. The Name property of a Tag must be unique within a given Tags collection. For example, you can't have two Tag objects named "Creation Date" in a single Shape object, but multiple Shape objects could each have a single Tag object named that.
The Value property of a Tag object is where things get interesting. It's data type is Variant, which means it can store any data type except fixed-length strings. (For more information about the Variant data type, see this reference topic.) So you can store pretty much any kind of information you want, including user-defined types.
Here's another interesting aspect of tags: The Tags collection is one of the rare pieces of the Publisher object model that doesn't have a counterpart in the application's user interface. (The only other instance of this I can think of is WizardTags.) Tags are a totally programmatic feature: you can't get or set Tag objects except through code. So unless you explicitly expose them in the user interface (by displaying them in a dialog box, for example) or the user writes code to find them, the average user isn't going to be aware of them at all. Which makes them an ideal place to store information you don't want the average (that is, non-programming) user to have easy access to.
To add a Tag object, use the Tags.Add method, which takes the Tag object name and value as parameters:
ActiveDocument.Tags.Add "CreationDate", CStr(Now)
So what kind of information can you store in tags? Pretty much anything you want. Seriously, the more I play around with tags, the more ideas I get for how to use them. I'm in the process of implementing some of these ideas in code. I'll post code samples as I complete them. For now, consider the following example to get your mind thinking about how you could use Tags in your business processes:
Suppose you're creating publications for a number of projects. You might want to add a Tag object to each publication to identify which project it's for. So you add a Tag object, named for the project, that has an initial value of "Pending":
ActiveDocument.Tags.Add "Farble Project", "InProcess"
When the project is done, you change the value to "Complete". Then, you write a procedure that goes through your various work folders once a week and moves any publications where Tag.Value="Complete" to a 'Completed Projects' folder.
Here's the hierarchy of the Tags object model section:
In my next few entries, we'll look at examples using Tags at the publication, page, and shape levels. See you then.
In my last entry, I talked about the Tags collection, which you can use as generic holding bins for whatever data you want to persists within your publications, pages, or shapes. Now let's look at some actual examples.
So far, the examples I've been able to think up use tags for storing two general types of information:
· Publication, page, or shape attribute information that isn't already stored in any of the available object model properties.
· Variable data that is needed for running some procedure or add-in functionality.
Example Using a Document-Level Tag
Suppose you've created a catalog of your products, including prices that are subject to change over time. You want to be sure none of your employees prints the catalog for a customer once it's more than three months old. So when you've finished creating the catalog, you add a document tag, called Expiration Date, and set its value to a date 90 days in the future.
ActiveDocument.Tags.Add "ExpirationDate", CStr(Now + 90)
And then you write an event handler for opening the document. The event handler compares the value of the Expiration Date tag with the current date. If the publication is opened after the expiration date, a dialog box displays advising the user not to print the catalog because the prices may not be accurate anymore.
(You'd place this event handler in the ThisDocument object module.)
Private Sub Document_Open()
If CDate(ActiveDocument.Tags("ExpirationDate").Value) < Now Then
MsgBox Prompt:="Prices in this catalog may be out of date." & _
vbLf & "Do not release this catalog to a customer" & vbLf & _
"without checking that prices are accurate.", _
Title:="Expiration Date Passed"
Example Using a Page-Level Tag
By design, when you perform a catalog merge, the publication you create or append with the merge results contains no information about the merge publication or data source used to create it. It has no 'memory' of having been created by a catalog merge.
But what if you wanted to retain that information in the target publication? Suppose you're building a single publication as a catalog, and you're using catalog merges from several different publication and/or data sources. (Maybe each section of your catalog uses a different data source, or each section has a different layout and so uses a different catalog merge area.) It'd be reasonable to want to know which pages in the publication where created with which data sources and publications, and when.
The following example uses page-level tags to store this information.
First, it opens the target publication and records how many pages are in the publication prior to the merge being executed. Then it executes the merge, appending the resulting pages to the target publication. Next, the code adds three tags to each new page created by the merge. These tags contain the following information:
· The data source used in the merge
· The publication that contains the catalog merge area used in the merge
· Date and time of the merge
The code even creates or increments a document-level tag called "No of Merges", which contains the number of merge results that have been appended to the target publication.
Function ExecuteMergeAndTagPages(filePath As String)
Dim d As Document
Dim p As Page
Dim intPages As Integer
Dim i As Integer
Dim t As Tag
Dim TagExists As Boolean
TagExists = False
Set d = Application.Open(filePath)
intPages = d.Pages.Count
Set d = ActiveDocument.MailMerge.Execute(Pause:=False, _
For i = (intPages + 1) To d.Pages.Count
Set p = d.Pages(i)
.Add "Data Source", ActiveDocument.MailMerge.DataSource.Name
.Add "Merge Pub", ActiveDocument.FullName
.Add "Page Creation Date", CStr(Now)
For Each t In d.Tags
If t.Name = "No of Merges" Then
t.Value = t.Value + 1
TagExists = True
If TagExists = False Then
d.Tags.Add Name:="No of Merges", Value:=1
Once you've stored this information, of course, you can write code that programmatically queries the tags and takes action based on the results. For example, you could write a procedure that deletes all the pages created using a certain data source.
Next time, I'll see if I can't come up with some code samples that use tags at the individual shape level.
First, I discussed the tags collection, which you can use as generic holding bins for whatever data you want to persists within your publications, pages, or shapes.
Next, I showed you a few examples using document and page-level tags.
So now, to round things out, let’s look at a short example using tags stored in shapes.
Let go back to the book example I mentioned in the first entry: you’ve created a book, and each chapter is a separate publication. Now, let’s further suppose that within each chapter you’re using textbox shapes for a number of different functions: the main chapter text, but also side-bar stories, pull quotes, figure captions, that kind of thing. Programmatically, there’s no way to tell the function of each textbox shape. Except you used tags to do exactly that. When you created each publication, you added a Tag object to each text box that denoted what you’re using it for. Like so:
Activedocument.Selection.ShapeRange(1).Tags.Add "StoryType", "PullQuote"
(The above code assigns the tag to the first shape in the ShapeRange you currently have selected in the active document. Obviously, if you’ve only got one shape selected, then that’s shape one.)
Now, suppose you decide later that you want to change the font you used for all the pull quotes, and bump up the point size while you’re at it. You can quickly create a procedure that identifies all the text boxes containing pull quotes, based on the value of their ‘StoryType’ tag, and makes the appropriate changes.
Dim s As Shape
For Each d In Documents
For Each p In d.Pages
For Each s In p.Shapes
If s.Type = pbTextFrame Then
For Each t In s.Tags
If (t.Name = "StoryType" And t.Value = "PullQuote") Then
s.TextFrame.TextRange.Font.Name = "Arial"
s.TextFrame.TextRange.Font.Size = 20
These examples are fairly basic, but I hope you get the idea of just how useful and flexible Tag objects can be. I’m sure, once you start looking at your own business processes, you’ll see situations where using tags will come in handy.
The first of my five part series, ‘Programming Microsoft Office Publisher Made Easy’ went live yesterday. The series consists of five lessons aimed at users who have never coded before, and want some from-the-ground-up assistance in writing powerful, flexible macros using the Publisher object model.
Here’s a list of the content and tentative publish dates for the five articles:
Lesson One (now live)
· Writing your first macro
· Macro security settings
· Sub procedures
Lesson Two (now live)
· Object-oriented programming
· Object variables
· Method parameters
· Creating event handlers
Lesson Three (now live)
· Creating dialog boxes
· If…Then statements
· Data variables
Lesson Four (now live)
· Object collections
· For Each…Next loops
· Nesting code statements
· Indenting code
Lesson Five (November 2nd)
· Return types
· Using the Object Browser
· Using the online help
And remember, if you read an article, please take a minute and rate it to let us know what you thought.
Afterwards, if you want some more background on Visual Basic and object-oriented programming, check out these two previous blog entries:
Publisher Programming Made Easy: Why Program with Objects, Anyway?
Publisher Programming Made Easy: Types of Visual Basic Statements
Just a quick, shameless plug:
Do you have things that you do repeatedly in Publisher? Do you find yourself doing a certain set of tasks over and over again? Wouldn't it be great if you could tell Publisher to perform complicated tasks automatically? Guess what? You can. There's a way to give Publisher detailed instructions on what you want it to do to, when you want it to do it, and even under what conditions, and have Publisher carry out all your instructions automatically.
You can do all that, and more, by writing macros. And the best part is, writing macros is easy. Give me a few minutes of your time and I'll show you how.
I've written a series of lessons that teach you what you need to know to start writing macros today. Each self-contained lesson introduces core programming concepts, with plenty of examples and hands-on exercises to show you what we're talking about. You don't need any special experience, just a computer with Publisher installed. After each lesson you'll have learned skills you can start using in your macros right away.
So why wouldn't you give it a try?
Here's links to the first four lessons:
Here's something I didn't notice until I'd been programming with Publisher for awhile:
You might have noticed that in Publisher 2003, we added a Documents collection to the Application object. But, you might ask, isn't Publisher a single document interface (SDI) application? If so, shouldn't each open document have its own separate Application object?
And you'd be right on all counts. Publisher is indeed an SDI application, so each open document has its own unique Application object that you can use in code. However, the Documents collection is our way of providing you the advantages that come with a multiple document interface (MDI) in the developer environment. It gives you a simple, easy way of identifying and iterating through all the publications open on a system. This especially comes in handy when you're doing the following:
· Moving content between publications
· Performing work on several publications at a time
Here's an example of how using the Documents collection simplifies moving content between publications. The code below cuts a shape from the first page of one publication, and then pastes it onto the second page of another publication.
Handling two separate Publisher application is fairly straight-forward. But things can get tricky when you have multiple instances of Publisher open, and you want to coordinate action between them. That's where the Documents collection comes in handy. For instance, suppose you've created a book, and that each chapter is a separate publication. Using the Documents collection, you can easily selected the publications you want to print, in the order you want to print them to produce a complete copy of your book.
Or suppose you want to create a function that iterates through all the publication you have open, and saves copies of any Web publications as HTML into a specific folder:
Function SaveOpenPubsAsHTML(FilePath As String)
For Each d In Application.Documents
If d.PublicationType = pbTypeWeb Then
d.SaveAs Filename:=FilePath & d.Name, _
(By the way, Publisher automatically adds the .htm file extension to the filename when you set the Format parameter to pbFileHTMLFiltered.)
A lot easier than having to keep track of all the individual application instances of Publisher, isn't it?