Tamar E. Granor, Ph.D.
Visual FoxPro's Report Designer is fine in many cases, but it's not hard to come up with reports that go beyond its capabilities. Automation gives you the ability to use all of Microsoft Word as your report writer. This session shows you how to put Word to work for you. It starts with the basics of creating a Word automation object, and then explores the Word object model to create both simple and complex Word documents. Learn how to send VFP data to Word and format it.
This session assumes some familiarity with Word, an understanding of object-oriented programming, and knowledge of automation fundamentals.
To work with Word, you need an object reference to the Word automation server or to a specific object in Word. Two Visual FoxPro functions let you access Word. CreateObject() opens a new instance of Word and returns a reference to it, like this:
oWord = CreateObject("Word.Application")
GetObject() is a little more complex. It takes either of two parameters, which reflect two approaches to providing a reference to Word. If you pass it the name of an existing document, it checks whether or not Word is already open. If not, it opens Word. Either way, it opens the specified document and returns a reference to the document:
oDocument = GetObject("d:\writing\confs\aut250.doc")
If you omit the first parameter to GetObject() and specify the Word automation server as the second, it looks for an open instance of Word and returns a reference to that instance:
oWord = GetObject(, "Word.Application")
If Word isn't open, an error message is generated.
The key object in Word is Document, which represents a single, open document. The Word server has a Documents collection, containing references to all open documents. The server also has an ActiveDocument property that points to the currently active document.
The Document object has lots of properties and methods. Many of its properties are references to collections such as Paragraphs, Tables and Sections. Each of those collections contains references to objects of the indicated type. Each object contains information about the appropriate piece of the document. For example, the Paragraph object has properties like KeepWithNext and Style, as well as methods like Indent and Outdent.
The Word server object, called Application, has its own set of properties and methods, including a number of other collections. In addition to ActiveDocument, the Application object's properties include Visible, StartupPath, Version and WindowState. The Application object also has methods. The simplest is Quit, which shuts down the server. It has several optional parameters – the first indicates what to do if any open documents have been changed and not saved. Other methods of the application object convert measurements from one set of units to another, check grammar and spelling, and much more.
Word Visual Basic Help (VBAWRD9.CHM for Word 2000, VBAWRD8.HLP for Word 97) contains a diagram of Word's object model. The figure is "live" – when you click on an object, you're taken to the Help topic for that object. Figure 1 shows the portion of the object model diagram that describes the Document object.

Figure 1. Word Object Model – The Word Visual Basic Help file offers a global view of Word's structure.
The methods for creating, opening, saving and closing documents are fairly straightforward. The only complication is in what object provides which method. The methods for creating and opening a document belong to the Documents collection. The methods for saving and closing a document belong to the Document object. Although confusing at first glance, this actually makes sense since you don't have a Document object to work with at the time you create or open a document. But when it's time to save or close it, a document reference is available.
To open an existing document, use the Open method of the Documents collection. Open has many parameters, but most of the time, the only one that matters is the first: the name and path of the file to open. The Word server doesn't recognize VFP's search path, so you usually need to provide the full path to the document, like this:
oDocument = oWord.Documents.Open("d:\writing\confs\aut250.doc")
If it's successful, Open returns an object reference to the newly opened document. If the specified document doesn't exist, an error is generated and nothing is returned. (Almost all the examples in this session assume you have an instance of Word running, with oWord holding a reference to it.)
To create a new document, use the Add method, which has only two, optional, parameters. The important one is the first, which indicates the path to the template on which the new document should be based. If it's omitted, the new document is based on the Normal template. (The Normal template is the one used when you click on the New button on Word's Standard toolbar.)
Like Open, Add returns a reference to the newly created document. This line creates a new document based on a template called "OfficeFax".
oDocument = oWord.Documents.Add( ;
"C:\WINNT\Profiles\Tamar\Application Data\Microsoft\OfficeFax.DOT")
As with the file name in Open, the full path to the template is needed.
The Document object has two methods for saving files. Save saves the document back to its current file; if it's never been saved, a Save As dialog box appears. The SaveAs method lets you specify the file name (and a lot of other stuff) without seeing a dialog, which is usually what you want in automation code.
If the currently active document has already been saved, this line resaves the document without user intervention:
oWord.ActiveDocument.Save()
To save the document to a different file or to save a document for the first time without displaying a dialog to the user, call SaveAs and pass the file name, like this:
oWord.ActiveDocument.SaveAs("D:\Documents\ThisIsNew.DOC")
Be careful. When you pass a file name to SaveAs, it overwrites any existing file without prompting. (Of course, SaveAs, like Word's other methods, doesn't respect VFP's SET SAFETY setting, since it's not running inside VFP.)
You can check whether the document has been saved by testing its Saved property. If Saved is .T., the document is unchanged. This can be the case either because you've already saved the document and haven't changed it since or because it's a new document and it hasn't yet been modified.
The Name and FullName properties give you an alternative way to check whether a document has ever been saved. When you create a new document, Word assigns a name in the form "Documentn" where n is a number. When you save the document, you can give it a more meaningful name, as well as specifying the file path. The Name property of the Document contains just the file stem with no path or extension. The FullName property contains the complete file name, including path and extension. However, before the file is saved for the first time, both Name and FullName contain the same string, the initial document name assigned by Word. You can use code like this to figure out whether to use Save or SaveAs:
WITH oWord.ActiveDocument
IF .Name = .FullName
* Prompt user to get a name,
* then:
.SaveAs( cFileName )
ELSE
.Save
ENDIF
ENDWITH
To close an open document, call the document's Close method:
oDocument.Close()
or:
oWord.ActiveDocument.Close()
Most of what you want to do with Word involves adding to, modifying or reading a document, whether it's a new document you're building or an existing document you're modifying. There are a variety of ways to do these things, but the key to just about all of them is the Range object and, to a lesser extent, the Selection object.
The Selection object represents the currently highlighted (that is, selected) portion in a document. If nothing is highlighted, Selection refers to the insertion point. There's only one Selection object, accessed directly from the Word application object. For example, to find out how many paragraphs are in the current selection, you can use this code:
nParagraphs = oWord.Selection.Paragraphs.Count
A Range object can represent any portion of a document. Ranges are not the same as the Selection area. You can define or modify Ranges without affecting the current Selection. You can even define multiple ranges for a document, whereas only one Selection object is available for each document. Ranges are very useful for repeatedly referencing specific portions of a document.
Ranges can be obtained in many ways. Many Word objects, like Sentence and Paragraph, have a Range property that contains an object reference to a Range object for the original object. For example, to create a Range from the third paragraph of the active document, you can use:
oRange = oWord.ActiveDocument.Paragraphs[3].Range
The Document object has a Range method that lets you specify a range by character position. For example, to get a reference to a Range containing the 100th to 250th characters in the active document (probably not a particularly useful range), use:
oRange = oWord.ActiveDocument.Range(100,250)
Document's Content property contains a reference to a Range consisting of the entire main document (the body of the document without headers, footers, footnotes, and so on). So the next two commands are equivalent:
oRange = oWord.ActiveDocument.Range()
oRange = oWord.ActiveDocument.Content
Beware: for a large document, creating such a variable can take a significant amount of time.
It's easy to convert a Range object to a Selection, and vice-versa. Like many other objects, Selection has a Range property, which provides a Range object from the Selection. Similarly, the Range object has a Select method that highlights the range's contents, turning it into the Selection. For example, to highlight the range from the previous example, use:
oRange.Select()
Selection and Range seem quite similar and are in many ways, but there are differences. The biggest, of course, is that you can have multiple Ranges, but only one Selection. In addition, working with a Range is usually faster than working with a Selection. On the whole, Word VBA experts recommend using Range rather than Selection wherever possible. The main reason is that using Selection is essentially duplicating screen actions with code; Range lets you operate more directly. Word's Macro Recorder tends to use the Selection object; this is one thing to be aware of when converting Word macros to VFP code.
The Text property of Range and Selection contains whatever text is in the specified area. To bring document contents into FoxPro, create an appropriate Range and read its Text property, like this:
oRange = oWord.ActiveDocument.Paragraphs[7].Range
cParagraph7 = oRange.Text
Text also lets you add or change the document contents. You can add text by assigning it to the Text property.
oRange.Text = "This is a new sentence."
You can also add text to whatever's already there. Simple text manipulation does the trick.
oRange.Text = oRange.Text + "Add text at the end."
or
oRange.Text = "Add text at the beginning " + oRange.Text
Another possibility is to read text into VFP, manipulate it in some way and write back.
cMakeUpper = oRange.Text
cMakeUpper = UPPER(cMakeUpper)
oRange.Text = cMakeUpper
That example can be shortened to a single line, like this:
oRange.Text = UPPER(oRange.Text)
While you can send VFP data to Word by manipulating the Text property, that isn't really the best way to do it. There are a number of Word methods designed for adding text to a range. The method you'll use the most is InsertAfter – it adds the text you pass to the end of the range and enlarges the range to include the new text. (There's also a corresponding InsertBefore method that adds the text at the beginning of the range.) InsertParagraphAfter adds a paragraph mark at the end of the range and expands the range to include it.
The following code sends the data from a record to a new, blank document:
USE _samples + "\TasTrade\Data\Customer"
LOCAL oDocument, oRange
oDocument = oWord.Documents.Add()
oRange = oDocument.Range()
oRange.InsertAfter(Customer_ID + ": " + Company_Name )
oRange.InsertParagraphAfter()
oRange.InsertAfter("Attn: " + TRIM(Contact_Name) + " - " + Contact_Title )
oRange.InsertParagraphAfter()
oRange.InsertAfter(Address )
oRange.InsertParagraphAfter()
oRange.InsertAfter(TRIM(City) + " " + TRIM(Region) + Postal_Code )
oRange.InsertParagraphAfter()
oRange.InsertAfter(UPPER(Country))
Figure 2 shows the resulting document.

Figure 2. This return address can be sent to Word many different ways. In this case, with no special formatting involved, the fastest is to build the whole string in VFP, then send it to Word.
Several other methods help you navigate within the range or document, so that text can be added exactly where you want it. One of the simplest is the Move method, which changes the boundaries of the range or selection.
Move accepts two parameters. The first indicates the unit of movement – you can move by characters, words, paragraphs, rows in a table, or the whole document. The second parameter tells how many of the specified units to move – a positive number indicates forward movement (toward the end of the document), while a negative number means backward movement (toward the beginning of the document).
In all cases, the range or selection is reduced (or "collapsed," in Word VBA terms) to a single point before being moved. While collapsing a range or selection sounds dire, it's not. The text contained in the range/selection remains in the document—only the extent of the range or selection is changed. When moving forward, the range or selection is reduced to its end point, then moved; when moving backward, it's reduced to its beginning point before moving. You don't need to do anything special afterward. For the automation programmer, the key issue is to understand where in the range movement begins.
Constants from the wdUnits group are used for the units of movement. Table 1 shows the values for this group that can be passed to the Move method. (Keep in mind that Word's constants are not available in VFP. You have to #DEFINE them before using them.)
Table 1. Word units. The constants in the wdUnits group represent portions of a document.
|
Constant |
Value |
Description |
|
wdCharacter |
1 |
One character. |
|
wdWord |
2 |
One word. |
|
wdSentence |
3 |
One sentence. |
|
wdParagraph |
4 |
One paragraph. |
|
wdSection |
8 |
One section of a document. (Word allows you to divide documents into multiple sections with different formatting.) |
|
wdStory |
6 |
The entire length of whichever part of the document you're in. Word considers the main body of the document to be one "story," the header to be another "story," the footnotes to be a third and so forth. |
|
wdCell |
12 |
One cell in a table. |
|
wdColumn |
9 |
One column of a table. |
|
wdRow |
10 |
One row of a table. |
|
wdTable |
15 |
The entire space of a table. |
To create a range at the end of the document, you can use this code:
oRange = oWord.ActiveDocument.Range()
oRange.Move( wdStory, 1)
The Move method provides another way to create the return address in Figure 2:
#DEFINE CR CHR(13)
#DEFINE wdStory 6
USE _samples + "\TasTrade\Data\Customer"
LOCAL oDocument, oRange
oDocument = oWord.Documents.Add() && Use the Normal template
oRange = oDocument.Range()
oRange.Text = Customer_ID + ": " + Company_Name + CR
oRange.Move(wdStory)
oRange.Text = "Attn: " + TRIM(Contact_Name) + " - " + Contact_Title + CR
oRange.Move(wdStory)
oRange.Text = Address + CR
oRange.Move(wdStory)
oRange.Text = TRIM(City) + " " + TRIM(Region) + Postal_Code + CR
oRange.Move(wdStory)
oRange.Text = UPPER(Country)
The Collapse method lets you explicitly reduce a range or selection to a single point. It takes one parameter, indicating the direction of the collapse. Passing the constant wdCollapseEnd (with a value of 0) collapses the range or selection to its end point (the point closest to the end of the document). Passing wdCollapseStart (whose value is 1) reduces the range or selection to its starting point. As with the Move method, Collapse doesn't remove content from the range. It changes the definition of the range to just a single point at what was previously either the end or beginning of the range.
The example can be rewritten yet again to use Collapse to control the range:
#DEFINE CR CHR(13)
#DEFINE wdCollapseEnd 0
USE _samples + "\TasTrade\Data\Customer"
LOCAL oDocument, oRange
oDocument = oWord.Documents.Add() && Use the Normal template
oRange = oDocument.Range()
oRange.Text = Customer_ID + ": " + Company_Name + CR
oRange.Collapse(wdCollapseEnd)
oRange.Text = "Attn: " + TRIM(Contact_Name) + " - " + Contact_Title + CR
oRange.Collapse(wdCollapseEnd)
oRange.Text = Address + CR
oRange.Collapse(wdCollapseEnd)
oRange.Text = TRIM(City) + " " + TRIM(Region) + Postal_Code + CR
oRange.Collapse(wdCollapseEnd)
oRange.Text = UPPER(Country)
Finally, it's worth commenting that, for this particular task, the fastest approach of all is to concatenate all the strings in VFP, then send one string to the document:
USE _samples + "\TasTrade\Data\Customer"
LOCAL oDocument, oRange
oDocument = oWord.Documents.Add() && Use the Normal template
oRange = oDocument.Range()
LOCAL cText
cText = ""
cText = Customer_ID + ": " + Company_Name + CR
cText = cText + "Attn: " + TRIM(Contact_Name) + " - " + Contact_Title + CR
cText = cText + Address + CR
cText = cText + TRIM(City) + " " + TRIM(Region) + Postal_Code + CR
cText = cText + UPPER(Country) + CR
oRange.Text = ""
oRange.InsertAfter(cText)
With VFP's speed at constructing strings, this version takes only one-third to one-quarter as long as the other approaches.
If all we could do was send text to Word and read the text already there, Automation would be useful, but not worth too much trouble. However, there's much more to automating Word than just sending and receiving text. One of the big benefits of using Word, rather than VFP, is the ability to apply complex formatting to documents.
Word allows documents to be formatted in a number of ways and the objects available for formatting reflect the way Word structures its commands. For example, the Font object contains properties for the settings found in Word's Font dialog (Format|Font on the menu). The ParagraphFormat object controls the settings found in the Paragraph dialog, such as indentation, spacing and alignment. Similarly, the settings from the Page Setup dialog are controlled by the PageSetup object. Style objects represent the preformatted and user-defined styles available in the document. These four objects manage most of the frequently used settings. Other objects control other aspects of formatting.
Fonts in Word are controlled by the Font dialog on the Format menu (shown in Figure 3). That dialog controls the font name, size, style (such as bold, italic, underline, and so forth), color, and effects (like strikethrough, superscripts and subscripts, and much more). It also controls more esoteric options such as kerning, animation of or around text, the vertical position of text with respect to the baseline, spacing between characters, and more. The Font object manages these options.

Figure 3. Specifying Fonts. The Font dialog controls font, size, style, and color, as well as unusual options like kerning and spacing between characters. In Automation, all of these features are managed by the Font object.
Range, Selection and Style (discussed in Working with Styles) as well as many other objects each have a Font property that points to a Font object. Changing the properties of the Font object modifies the font of that portion of the document. For example, to change all the customer information in Figure 2 to 12-point Arial, you can use this code:
oRange = oDocument.Range()
oRange.Font.Name = "Arial"
oRange.Font.Size = 12
To simplify matters, just set the desired font before sending the text to the document. Add the two lines that set the font properties to the example program right after the line that sets oRange (as shown here) and the document will be created with 12-point Arial to begin with.
In fact, this isn't the best way to set the font for a whole document. It's better to use a template where the font of the Normal style has been set as needed. See Working with Styles below.
Table 2 lists Font properties you're likely to want to work with, along with Word constants for them, where appropriate.
Table 2. Font Properties. The Font object controls the appearance of the font from the font face to its size, style and much more. This table shows the more common properties. Check Help for more unusual settings.
|
Property |
Type |
Description |
|
Name |
Character |
The name of the font. |
|
Size |
Numeric |
The size of the font in points. |
|
Bold |
Numeric or Logical |
Indicates whether the text is bold. |
|
Italic |
Numeric or Logical |
Indicates whether the text is italic. |
|
Underline |
Numeric |
The type of underline.
|
|
Superscript, Subscript |
Numeric or Logical |
Indicates whether the text is superscript or subscript. |
It's possible for the text in a range (or whatever area the font object covers) to have more than one font setting. When that happens, the various numeric properties get the value wdUndefined (9999999). (That's also why properties that you'd expect to be logical are listed as numeric or logical.) Font.Name is the empty string in that situation. Although these logical properties (like Bold and Italic) can be set by assigning VFP's logical values .T. and .F., they can't be compared to logical values. Code like:
IF oFont.Bold
fails with the error "Operator/operand type mismatch". That's because of Bold's dual numeric/logical capabilities. When you assign logical values, Word translates them somewhere along the way, but for comparison, you have to use the numeric values.
Paragraphs are a key concept in Word. Much of Word's formatting can be thought of as being stored "in" the marker that follows each paragraph. That's why moving text sometimes changes its formatting. If you fail to take the paragraph marker along, the moved text picks up the formatting of the new location.
At the paragraph level, you can determine alignment of text (left, right, centered or full justification), various kinds of indentation (both amount and type), spacing of lines and paragraphs, handling of widows and orphans, and much more. Word allows regular indentation from both the left and right margins, as well as first line indents and hanging indents. Interactively, all of this is managed by the Paragraph dialog on the Format menu (shown in Figure 4). Behind the scenes, the ParagraphFormat object controls these settings.

Figure 4.The Paragraph Format dialog. This page lets you indicate alignment, indentation and spacing. The second page handles widow and orphan control and automatic hyphenation. The ParagraphFormat object controls these settings for automation.
Range, Selection and Style, among others, have a ParagraphFormat object, accessed through the same-named Property. The Paragraph object has a Format property that accesses a ParagraphFormat object.
Table 3 shows commonly used properties of the ParagraphFormat object and frequently used constant values for them. Like many of Word's objects, ParagraphFormat has only a few methods; none are likely to be useful in most automation code.
Table 3. ParagraphFormat Properties. Paragraphs are a key entity in a document. See Help for more esoteric settings.
|
Property |
Type |
Description |
|
Alignment |
Numeric |
The alignment of text in the paragraph.
|
|
LeftIndent |
Numeric |
The indentation of the left edge of this paragraph from the left margin in points. |
|
RightIndent |
Numeric |
The indentation of the right edge of this paragraph from the right margin in points. |
|
FirstLineIndent |
Numeric |
The indentation of the first line of the paragraph. This property determines whether the paragraph has the first line indented or "outdented" (providing a hanging indent). Set a positive value to indent the first line, 0 to keep the first line flush with the rest of the paragraph or a negative value for a hanging indent. Note that, with a hanging indent, the first line doesn't move to the left; subsequent lines move to the right. |
|
SpaceBefore, SpaceAfter |
Numeric |
The amount of white space (known as leading) before and after the paragraph, in points. |
|
LineSpacingRule |
Numeric |
The kind of line spacing in effect. This setting can entirely determine the line spacing or it can set the stage for LineSpacing.
|
|
LineSpacing |
Numeric |
When LineSpacingRule is wdLineSpaceAtLeast, wdLineSpaceExactly, or wdLineSpaceMultiple, the actual line spacing in points. |
|
WidowControl |
Numeric or Logical |
Indicates whether the first and last lines of the paragraph are kept on the same page as the rest of the paragraph. |
|
KeepTogether |
Numeric or Logical |
Indicates whether the entire paragraph is kept on a single page. |
|
KeepWithNext |
Numeric or Logical |
Indicates whether the paragraph is kept on the same page with the paragraph that follows it. |
|
Hyphenation |
Numeric or Logical |
Indicates whether the paragraph is hyphenated automatically |
This example sets the first paragraph in the range oRange to have a 0.5" first line indent, widow and orphan control, double spacing, and full justification:
#DEFINE wdAlignParagraphJustify 3
#DEFINE wdLineSpaceDouble 2
WITH oRange.Paragraphs[1].Format
.FirstLineIndent = oWord.InchesToPoints( .5 )
.WidowControl = .T.
.Alignment = wdAlignParagraphJustify
.LineSpacingRule = wdLineSpaceDouble
ENDWITH
This example triple spaces a range:
#DEFINE wdLineSpaceMultiple 5
WITH oRange.ParagraphFormat
.LineSpacingRule = wdLineSpaceMultiple
.LineSpacing = oWord.LinesToPoints( 3 )
ENDWITH
While it's appropriate to manually adjust the formatting of a word, sentence or paragraph here or there, the most effective way to use Word is to take advantage of styles, which are named formats that you can apply to a portion of a document. When you're working interactively in Word, you can see the style for the insertion point or currently highlighted text in the first dropdown on the Formatting toolbar.
Word has two kinds of styles: paragraph styles and character styles. Character styles are used for fragments and control only a few settings, primarily font-related. Paragraph styles, as the name implies, apply to entire paragraphs and include a lot more options. Paragraph styles can specify font and paragraph formatting, as well as tab settings and much more. In the Style dialog available from Word’s Format menu (Figure 5), Paragraph styles are preceded with a paragraph marker while character styles begin with an underlined "a".

Figure 5. Creating and choosing styles. Word's Style dialog lists the available styles. Paragraph styles are preceded by a paragraph symbol, while an underlined "a" precedes Character styles. Note in the Description that the style is described in terms of another – its "base style".
Using styles is much like using classes in an object-oriented language. They make it easy to enforce uniformity throughout and across documents and let you change the characteristics of sections of text with a single change. Word's styles offer some other benefits, as well. For example, each paragraph style sets the default style for the paragraph to follow.
The Document object includes a Styles collection, containing one Style object for each of the styles stored in the document. You can add your own styles using the Style collection’s Add method. Various objects' Style properties point to Style objects.
What this means is that, rather than writing a lot of code to change fonts and sizes, and to set alignment and leading and other things like that, you can simply define a few custom styles or modify built-in styles, then apply them to your documents as needed. For example, this code modifies the Normal style, which is always available, to use 16-point centered Garamond Italic:
#DEFINE wdStyleNormal –1
#DEFINE wdAlignParagraphCenter 1
WITH oWord.ActiveDocument.Styles[ wdStyleNormal ]
WITH .Font
.Name = "Garamond"
.Size = 16
.Italic = .T.
ENDWITH
.ParagraphFormat.Alignment = wdAlignParagraphCenter
ENDWITH
To apply an existing style to a portion of a document, set the Style property for the Range or Paragraph to a built-in style using a constant, or to the name of a custom style. Table 4 lists the constants for some of the more commonly used built-in styles. This example applies the Heading 1 style to the range referenced by oRange:
#DEFINE wdStyleHeading1 -2
oRange.Style = oWord.ActiveDocument.Styles[ wdStyleHeading1 ]
Table 4. Built-in Styles. Word has over 100 built-in styles, each referenced by a defined constant. The table shows just a few of the most common. Use the Object Browser to find the rest.
|
Constant |
Value |
Constant |
Value |
|
wdStyleNormal |
-1 |
wdStyleHeading1 |
-2 |
|
wdStyleBodyText |
-67 |
wdStyleHeading2 |
-3 |
|
wdStyleDefaultParagraphFont |
-66 |
wdStyleHeading3 |
-4 |
In addition to modifying the built-in styles, you can create your own custom styles. To add a new style, use the Add method of the Styles collection. Add takes two parameters, the name of the new style, and the Word constant indicating whether it’s a paragraph style (wdStyleTypeParagraph = 1) or character style (wdStyleTypeCharacter = 2).
Every style is based on an existing style. By default, new paragraph styles are based on the Normal style and new character styles are based on the Default Character Font style. The BaseStyle property indicates which style another style inherits from, however.
Whatever style BaseStyle points to, all other changes to the style's properties use the BaseStyle as their point of reference. In the Style dialog (Figure 5), the style's characteristics are described as "<The base style>"+"<various other characteristics>". The Description property contains the same information. So, much like classes in OOP, changes to the base style change styles based on it.
Table 5 lists key properties of Style, along with significant constant values.
Table 5. Style Counts. Styles are Word's version of OOP. They offer a way to provide uniform formatting within and across documents.
|
Property |
Type |
Description |
|
BaseStyle |
Character, Numeric or Object |
The name, constant value or pointer to the style on which this style is based. See Table 4 and Help or the Object Browser for constant values for built-in styles. |
|
Type |
Numeric |
Indicates whether this is a paragraph or character style.
|
|
Builtin |
Logical |
Indicates whether this is a built-in style. |
|
Description |
Character |
The description of the style (as shown in the Style dialog). |
|
Font |
Object |
Pointer to a Font object for the style. |
|
ParagraphFormat |
Object |
Pointer to a ParagraphFormat object for the style. |
|
Borders |
Object |
Pointer to a Borders collection for the style. |
|
Shading |
Object |
Pointer to a Shading object for the style. |
|
NextParagraphStyle |
Character, Numeric or Object |
The name, constant value or pointer to the style for the paragraph to follow this paragraph, for paragraph styles. |
This example takes the simple customer address document in Figure 2 and begins to create a document worthy of Word. It creates several new styles to do the job. In practice, you could use the built-in Normal and Heading X (there are multiple heading levels) styles for this document, redefining them as needed. But the example shows how easy it is to create new styles. (You'll find this program as Styles.Prg in the materials for this session.)
* Styles.PRG
* © 2000, Tamar E. Granor and Della Martin
* From: Microsoft Office Automation with Visual FoxPro
* Hentzenwerke Publishing. www.hentzenwerke.com
* Create a formatted document by sending data from one record.
* Demonstrates Style objects, but it's more likely the needs here
* could be met by existing styles.
#DEFINE CR CHR(13)
#DEFINE wdStyleTypeParagraph 1
#DEFINE wdStyleNormal -1
#DEFINE wdAlignParagraphLeft 0
#DEFINE wdAlignParagraphCenter 1
#DEFINE wdCollapseEnd 0
USE _Samples + "TasTrade\Data\Customer"
LOCAL oWord, oDocument, oRange
LOCAL oBodyStyle, oMajorHeadingStyle, oMinorHeadingStyle
oWord = CreateObject("Word.Application")
oWord.Visible = .T.
oDocument = oWord.Documents.Add() && Use the Normal template
oRange = oDocument.Range()
* Set up styles. Base body style on Normal.
oBodyStyle = oDocument.Styles.Add( "Body", wdStyleTypeParagraph )
WITH oBodyStyle
* This line is overkill since it's the default
.BaseStyle = oDocument.Styles[ wdStyleNormal ]
WITH .Font
.Name = "Arial"
.Size = 12
ENDWITH
WITH .ParagraphFormat
* These are fairly normal defaults, so these lines
* may not be necessary
.Alignment = wdAlignParagraphLeft
.SpaceAfter = 0
ENDWITH
ENDWITH
* Major heading is big and centered.
oMajorHeadingStyle = oDocument.Styles.Add( "MajorHeading", wdStyleTypeParagraph)
WITH oMajorHeadingStyle
.BaseStyle = oBodyStyle
.Font.Size = 20
WITH .ParagraphFormat
.Alignment = wdAlignParagraphCenter
.SpaceAfter = 6 && leave a line after
.KeepWithNext = .T. && include at least one line of next
&& paragraph before new page
.KeepTogether = .T. && keep the whole paragraph together
ENDWITH
ENDWITH
* Minor heading is just big.
oMinorHeadingStyle = oDocument.Styles.Add("MinorHeading", ;
wdStyleTypeParagraph )
WITH oMinorHeadingStyle
.BaseStyle = oBodyStyle
.Font.Size = 16
ENDWITH
* Now create customer report
* First, our company info centered at the top
oRange.Style = oMajorHeadingStyle
oRange.InsertAfter("Automation Sample Company" + CR)
oRange.InsertAfter("Factory Blvd." + CR)
oRange.InsertAfter("Robotville, PA 19199" + CR)
* Now leave some blank space, then put info about this customer
oRange.Collapse( wdCollapseEnd )
oRange.End = oRange.End + 1 && to allow assignment to font
oRange.Style = oBodyStyle
oRange.InsertParagraphAfter( )
oRange.InsertParagraphAfter( )
* Use minor heading for customer id and name
* Put customer id in bold
oRange.Collapse( wdCollapseEnd )
oRange.End = oRange.End + 1 && to allow assignment to font
oRange.Style = oMinorHeadingStyle
oRange.InsertAfter(Customer_ID + ": " + TRIM(Company_Name) + CR)
oRange.Words[1].Font.Bold = .t.
* Regular body style for address info
oRange.Collapse( wdCollapseEnd )
oRange.End = oRange.End + 1 && to allow assignment to font
oRange.Style = oBodyStyle
oRange.InsertAfter(TRIM(Contact_Title) + ":" + TRIM(Contact_Name) ;
+ CR)
oRange.InsertAfter(TRIM(Address) + CR)
oRange.InsertAfter(TRIM(City) + " " + TRIM(Region) + ;
Postal_Code + CR)
oRange.InsertAfter(UPPER(TRIM(Country)) + CR )
* Extra line for spacing
oRange.InsertParagraphAfter( )
* Back to minor heading for phone number
oRange.Collapse( wdCollapseEnd )
oRange.End = oRange.End + 1 && to allow assignment to font
oRange.Style = oMinorHeadingStyle
oRange.InsertAfter( "Phone: " + TRIM(Phone) + CR)
* Fax number in regular body style
oRange.Collapse( wdCollapseEnd )
oRange.End = oRange.End + 1 && to allow assignment to font
oRange.Style = oBodyStyle
oRange.InsertAfter( "Fax: " + TRIM(Fax) + CR )
Note the use of the Words collection to bold only the customer id rather than the whole line. Figure 6 shows the resulting document in Word.

Figure 6. Using Styles. Rather than formatting every item independently, styles let you define and name sets of formatting characteristics, then apply them uniformly within and across documents. Styles can be considered OOP for formatting.
It's worth noting that the code in the example is divided almost exactly evenly between formatting the styles and putting the data in the document. But far more lines would have been needed to perform the same formatting without using styles. Furthermore, consider what would be needed to add more information for the same customer. Once the styles are defined, they can be used over and over. With 42 lines of code, we have three styles that can be applied wherever we need them, no matter how many more lines of code we write to send text to the document.
Producing attractive documents is a good start, but users usually want output of some sort, too. Word has a number of methods for producing output. In the interactive product, they're collected on the File menu in the Print Preview and Print menu options.
The PrintOut method of the Document object automates printing. It accepts a huge number of parameters. Fortunately, all parameters are optional and you won't need most of those parameters for normal processing. Unfortunately, the ones you're most likely to need are in the middle of the list rather than up front. The key parameters for most situations are shown here. (Incredibly, there are still eight more parameters after these.)
oDocument.PrintOut( lBackground, , nPrintRangeType, cFileName, cFrom, cTo, ,
cCopies, cPageRange, , lPrintToFile)
|
lBackground |
Logical |
Indicates whether or not to print in the background, continuing with other code while printing. |
|
nPrintRangeType |
Numeric |
Indicates which part of the document to print. See below. |
|
cFileName |
Character |
If printing to file, the filename, including path, of the destination file. |
|
cFrom, cTo |
Character |
If nPrintRangeType is wdPrintFromTo, the beginning and end of the print range. See below. |
|
cCopies |
Character |
The number of copies to print. |
|
cPageRange |
Character |
If nPrintRangeType is wdPrintRangeOfPages, the print range. See below. |
|
lPrintToFile. |
Logical |
Indicates whether to print to a file. |
The syntax reflects two different approaches to specifying the pages to be printed. The nPrintRangeType parameter determines which, if either, is used. The valid constant values are wdPrintAllDocument (0), wdPrintSelection (1), wdPrintCurrentPage (2), wdPrintFromTo (3), and wdPrintRangeOfPages (4). When wdPrintFromTo is passed, pass values for cFrom and cTo as the fifth and sixth parameters, respectively. Note that, for reasons known only to Microsoft, they're passed as characters. For example, to print three copies of pages 4 to 10 of a document referenced by oDocument, use:
oDocument.PrintOut( , , wdPrintFromTo, , "4", "10", , "3")
The wdPrintRangeOfPages choice lets you specify a single string and has more flexibility. In that case, pass a value for cPageRange as the eighth parameter - it can include multiple comma-separated values; each may include a range. For example, "3, 4-6, 12".
To print to a file, you have to specify both the file name and a flag that you're printing to file, like this:
oDocument.PrintOut( , , , "fileoutput", , , , , , , .t.)
The resulting file is ready to print, complete with printer codes. Keep in mind that the file is stored in Word's current directory by default, so it's a good idea to provide the full path.
Be forewarned that printing to file sets the Print to File checkbox in the Print dialog and leaves it set. Omitting that parameter in a subsequent call to PrintOut defaults to .T.; you have to explicitly pass .F. to print to the printer.
Perhaps all your users want is to see how the document will look when printed. That's easy. Just call the Document object’s PrintPreview method. That switches Word to PrintPreview mode.
Of course, that's only useful if Word is visible. If Word is hidden, it doesn't do any good. Making Word visible is as easy as setting the Application’s Visible property to .T. Better yet, if you've been doing everything in the background and now you're ready to show the user what you've been up to, call PrintPreview, make Word visible, then call Word's Activate method. That'll bring Word to the front. Try it like this:
oDocument.PrintPreview && Get the document ready for the user to see
oWord.Visible = .t. && Show word
oWord.Activate() && Bring it to the front
Regardless of whether you're keeping Word hidden most of the time or showing it all along, when you're done with Print Preview, you turn it off by calling the ClosePrintPreview method of the Document object.
Word's tables seem like a natural fit for representing Visual FoxPro data. A row can represent a record, with each column representing a field. What Word buys you is the ability to format the data and the table itself in sophisticated ways, well beyond the capabilities of FoxPro's Report Designer, as well as letting users manipulate the results or produce output in alternative formats. A table can be formatted as a whole, or individual cells can be separately formatted. Borders of tables and cells can be visible or invisible, and can take on a range of sizes and styles. (The details of borders and shading are beyond the scope of this session – see Word VBA Help for information on the Border and Shading objects.) Both columns and rows can be individually sized.
The object hierarchy for tables is a little confusing. Each document has a Tables collection, which in turn contains individual Table objects. The Table object contains both Rows and Columns collections, which contain Row and Column objects, respectively. Those objects each have a Cells collection that references the individual cells in the row or column, each represented by a Cell object. While the Table object doesn't have a Cells collection, the individual Cell objects can be accessed using the Table's Cell method, which accepts row and column number parameters. Here are several ways to refer to the cell in the third row and fourth column of the first table in the active document:
oWord.ActiveDocument.Tables[1].Rows[3].Cells[4]
oWord.ActiveDocument.Tables[1].Columns[4].Cells[3]
oWord.ActiveDocument.Tables[1].Cell[3,4]
Interactively, you can add a table by choosing Table | Insert | Table from the menu, which brings up the dialog in Figure 7.

Figure 7. Adding a table. To add a table interactively, you specify the number of columns and rows and, if you want, whether the columns should resize automatically to fit their contents. You can also specify a predefined format for the table.
With Automation, since Tables is a collection, it's not surprising that the way to add a table to a document is to call the Add method of that collection. It takes several parameters:
oDocument.Tables.Add( oRange, nRows, nColumns, nTableBehavior, nAutoFit )
|
oRange |
Object |
Reference to a range indicating where to insert the new table. |
|
nRows |
Numeric |
The number of rows in the new table. |
|
nColumns |
Numeric |
The number of columns in the new table. |
|
nTableBehavior |
Numeric |
A constant indicating whether the table automatically resizes to fit its contents.
|
|
nAutoFit |
Numeric |
If nTableBehavior is wdWord9TableBehavior, indicates which algorithm is used to resize the cells.
|
The last two parameters are optional. If you omit them, you get the older (Word 97) behavior of a table that doesn't resize as you fill it. However, you can change that behavior. The AllowAutoFit property and AutoFitBehavior method control this resizing capability.
Even if you're not allowing automatic resizing of columns, the AutoFit method of the Column object lets you resize individual columns based on their content. Rather than having cells change size as data is entered, you apply the changes once you've put something into the table. Column's SetWidth method lets you set a column to a specific width, in points.
Once you've added a table interactively, the Table Properties dialog on the Table menu allows you to adjust various characteristics of the table as a whole and of the individual rows, columns and cells. Figure 8 shows the Row page of that dialog. With Automation, you use various properties of the Table, Row, Column and Cell objects and the Rows, Columns and Cells collections to make these adjustments.

Figure 8. Formatting a table. The Table Properties dialog lets you resize rows and columns, as well as specify other characteristics of tables and their components.
Table 6 shows the most commonly used properties of Table.
Table 6. Defining Tables. These properties of the Table object are the ones you're most likely to work with.
|
Property |
Type |
Description |
|
Rows |
Object |
Pointer to the Rows collection for the table. |
|
Columns |
Object |
Pointer to the Columns collection for the table. |
|
Uniform |
Logical |
Indicates whether every row has the same number of columns. Read-only. |
|
Borders |
Object |
Pointer to the Borders collection for the table. |
|
Shading |
Object |
Pointer to the Shading object for the table. |
|
AllowAutoFit |
Logical |
Indicates whether columns are automatically resized as data is added to the table. Corresponds to the nTableBehavior parameter of the Tables.Add method. |
|
AllowPageBreaks |
Logical |
Indicates whether the table can be split over multiple pages in the document. |
|
AutoFormatType |
Numeric |
A constant indicating which, if any, of a set of predefined formats has been applied to the table. AutoFormats are applied with the AutoFormat method. Here are a few:
|
|
Spacing |
Numeric |
Indicates the space between cells, in points. This is space that actually separates the cells, not the boundary between the cell border and the text. |
Row and Column, not surprisingly, have a number of properties in common, including Cells to point to a Cells collection and Shading to reference a Shading object. Row also has a Borders property that references a Borders collection, though Column does not. Both objects have logical IsFirst and IsLast properties that, as their names suggest, indicate whether the particular row or column is the first or last in the collection.
At this point, the two objects part company, though there are still similarities. The size of a Row is determined by HeightRule and Height, as indicated in Table 7. Column width also uses two properties, PreferredWidth and PreferredWidthType, shown in Table 8.
Row has one other size-related property, SpaceBetweenColumns. It indicates the distance between the cell boundaries and the text. The value of the property is half what you set in Word itself because that one is measured from the text in one cell to the text in the next cell.
Row's AllowBreakAcrossRows property determines what happens when the contents of a row don't fit on the current page. If it's .T., the row can be split over two pages; if .F., a page break occurs before the row.
When a table is split over multiple pages, rows whose HeadingFormat property is set to .T. are repeated.
One big difference between Row and Column is that a Row can be a Range while a Column cannot.
Table 7. Determining Row Size. Two Row properties combine to let you indicate the height of the row.
|
Property |
Type |
Description |
|
HeightRule |
Numeric |
Indicates the logic used to determine the height of this row. Use one of the following constants:
|
|
Height |
Numeric |
The height for the row, if HeightRule is wdRowHeightExactly. The minimum height for the row, if HeightRule is wdRowHeightAtLeast. Ignored (and uninformative when queried) if HeightRule is wdRowHeightAuto; in that case, the row height is based on the row's contents. |
Table 8. Specifying Column Width. These two properties combine to determine the width of a column.
|
Property |
Type |
Description |
|
PreferredWidth |
Numeric |
Desired width for this column, either in points or as a percentage of the overall window width. Interpretation is determined by PreferredWidthType. |
|
PreferredWidthType |
Numeric |
Indicates whether PreferredWidth is measured in points or percent, or is ignored.
|
Cell shares a number of properties of Table, Row and Column, including Borders, Shading, HeightRule, Height, PreferredWidth and PreferredWidthType. Table 9 shows some other properties unique to Cell.
Table 9. Cell holdings. At the bottom of the table hierarchy, cells have quite a few properties. Here are some you're likely to deal with.
|
Property |
Type |
Description |
|
Width |
Numeric |