Professional Documents
Culture Documents
Since Word 6.0, Word has provided a way to create an on-line form with its form fields,
from the Forms toolbar. Because these are native to Word, they work optimally in Word
documents to provide a means to enter data directly
into a
protected Word document, be it as text, in text form
Textbox formatted to fields,
True/False in checkboxes, or a selection from a
move with the text
predefined list, offered in a Dropdown field.
What form fields can do, however, is limited,
compared to the capabilities with which we have
become familiar in dialog boxes, UserForms, forms
created
in other applications such as Access or VB, or what we know from the Internet. In Word
97, Microsoft introduced VBA across the Office application suite, with some shared
interfaces; among them Drawing tools, CommandBars (toolbars) and UserForms. Some
of the ActiveX controls developed for UserForms were also made available on the
Controls Toolbox toolbar (Fig. 1) for use in the application interface. In this way,
commonly recognized controls such as command buttons, text boxes, option buttons,
checkboxes, combo boxes and list boxes can be inserted into a Word document (or an
Excel spreadsheet).
Unfortunately, simply inserting the controls into a document is often not enough to use
them effectively. Since they were not developed to work specifically in the Word
Figure 1. Word's
Control Toolbox
toolbar.
(ControlToolbox.bmp)
document interface, they do not always behave as you would expect or wish. This is also
important when it comes to automating them, whether from within the Word application
environment, or from outside it.
Note: You may be able to insert ActiveX controls whether from Microsoft or third-party
providers not found in the Control Toolbox into a Word document. But it is not certain
that they will function as expected, or that their COM interfaces will be available.
This article explores how ActiveX Controls from the Control Toolbox can be used
effectively in a Word document. In general, the information is valid for all current
versions of Word (97, 2000, 2002 and Word 11), but the specifics and code examples are
based on testing done in Word 2002.
document protection is in force. Otherwise, the mouse must be used, or VBA code in
the KeyDown or KeyUp events must control the navigation.
In order to navigate in a protected form with the TAB key, the ActiveX controls must
be positioned graphically in-line with the text; they may not be formatted with any
text wrapping options. If text-wrapping is used, then the mouse or VBA is required
for navigation.
Note: In Word 97, ActiveX controls are inserted by default with text wrapping. You must
hold down SHIFT in order to have them inserted in-line with the text, or they must be
formatted in-line with the text manually. In later versions of Word, the controls are
inserted in-line with the text by default.
If the focus is in an ActiveX control, and the document is protected as a form, the
Print Preview mode is not available. If a protected form contains only ActiveX
controls, and no regular form fields or unprotected sections, Print Preview will not be
available at all.
While all ActiveX controls from the Control Toolbox can be used in protected
sections of a Word form, other controls (such as the Microsoft Calendar control) will
probably not behave correctly. They may, however, work in unprotected sections.
The VBA properties of ActiveX controls are not always stable. For example, the
Name property, given by Word upon insertion of the control, may change
unexpectedly. Again, later versions of Word are more stable than earlier ones,
especially Word 97.
Building a form
A good way to learn how to deal with ActiveX controls is to build a sample form, like the
on in Figure 2, that is also provided as a download file. This form contains textbox
controls, a combo box list, a command button, option buttons, regular text input form
fields, and a calendar control.
Figure 2. Sample form document with ActiveX and form fields (Form.bmp)
Date field
Although this article is primarily about ActiveX controls, there are situations in which
regular form fields are useful. There are a couple of these in the example, for instance the
date.
The date at the top right of the letter is generated automatically by a text input form field.
The type of the form field is set to Current date. The date automatically appears in the
language specified in the Windows regional settings. This type of form field
automatically fills in the current date, and disables the field for user input.
Possible alternatives to this type of form field would be:
A regular text input form field. An AutoNew macro or Document_New event could
insert the current date into the field. An advantage of this method would be that the
user could change the date. And it would remain static, unless either of the macro
procedures were run again, or the user changed the date. The language would have to
be handled by the VBA code.
An ActiveX control, that could be handled as above. An advantage of this, or the
following approaches, is that the document would not necessarily have to be
protected as a form.
A bookmark, that a macro would fill.
A CreateDate field. The advantage to this method would be that the date would
remain static until the document name is changed using File/Save As. The date
language is determined by the language formatting of the document text. The
document need not be protected as a form.
In the case of the fields, the date format is controlled by formatting switches in the field
code. Where a macro inserts the date, the formatting must be part of the VBA code, and
the date will not change dynamically.
Textbox controls
The first text box control is meant for the recipient name and street address. Normally,
you'd use separate fields for this, but for demonstration purposes, the fields have been
combined; the user is expected to press Enter at least once. The question is: how do you
get a textbox to accommodate the entire entry?
Note: be sure to set the EnterKeyBehavior property to True for a control where the user
is expected to press Enter to create new lines.
If you look at the available properties for a textbox control, you'll see, among others,
AutoSize and Multiline. The latter enables the user to press Enter while typing in
the control, in order to get multiple lines. The former resizes the control in order to
display all the content. Both work as designed (AutoSize has some problems displaying
correctly in Word 97), but "as designed" may not be what you want in a Word document.
If Multiline is not active, AutoSize does what you'd wish and expect: the width of
the control is adjusted to the content. (Note that this functionality will not respect
document, column or table margins.)
With Multiline active, however, the width of the control will no longer change, only
the height will adapt to the content. And, should all the content be deleted at one point,
the control's width will narrow to display only a single character and stay there until the
document is unprotected and the width reset in the Design mode. Not exactly a frustration
you to which you want to submit your users!
In order to get the best of both worlds, some VBA code is required, such as in Listing 1.
There's really no reliable way to calculate exactly how much height a certain amount of
text requires; it can only be approximated, based on the font, its size and the actual input.
In this example, the font is Times New Roman (the default style in the document);
increasing the size by a factor of .2 does a pretty good job of figuring the line height.
Next task is to get the number of lines. In this case, we assume that each additional line is
defined by pressing Enter. All instances of vbCR (a carriage return) are determined in a
Do...Loop. Finally, the control height is calculated by multiplying the line count times
the font size factor, plus an arbitrary adjustment factor (here: 2) that must be determined
by testing.
Listing 1. Adjust the height of a textbox control to display all the lines.
Public Function AdjustControlHeight(o_ctl As Object)
Dim sFontSize As Single
Dim szEntry As String
Dim lLineCounter As Long
Dim lPos As Long
o_ctl.SelStart = 0
sFontSize = o_ctl.FontSize + (0.2 * o_ctl.FontSize)
szEntry = o_ctl.Text
lLineCounter = 1
Do While InStr(szEntry, vbCr)
lPos = InStr(szEntry, vbCr)
szEntry = Mid(szEntry, lPos + 1)
lLineCounter = lLineCounter + 1
Loop
o_ctl.Height = (lLineCounter * sFontSize) + 2
Exit Function
End Function
Note: If you have a choice, use a text input form field for multiline data that has to adjust
in both width and height. A form field is designed to fit into the text flow and will make
these adjustments with no problems, whatsoever.
For the control txtAddress, AutoSize is set to False, and Multiline is set to True. The
width of the control is set to the width of the table cell.
The fields for the city, state and postal code are not multiline, and theoretically wouldn't
need any support for setting their respective widths. Unfortunately, not all versions of
Word handle the autosizing of an ActiveX control in a document interface well. As a rule
of thumb, the older the version of Word, the less reliable Autosize will work.
Note: The postal code has its own control it order to facilitate extraction of data into a
database. Ideally, the State information should be in a separate field, as well, but for
demonstration purposes, the form was set up with just these two fields on this line.
The remaining ActiveX text box controls in the document follow the same pattern as the
txtCity and txtPostalcode fields.
There are two basic methods to pass items to a combo box control: using the .Item
method, or assigning an array to the List property. The sample code uses the latter
approach.
The control's Style property is set to fmStyleDropDownCombo so that the user can
type in something other than an entry in the list; and MatchRequired is set to False.
MatchEntry is set to frmMatchEntryComplete so that the list will quickly scroll to a
matching entry as the user types in a country designation. Pressing Tab will then select
that item and move to the next control.
wrap formatting.) If the controls are placed in frames (from the Forms toolbar), then the
Tab order corresponds to the order of the frames' anchors in the document.
In addition, there is no SetFocus method in the Word document environment.
This makes changing the order in which ActiveX controls are selected a bit of a
challenge. It's especially critical if data validation is required, where the user should be
returned to a control if the content is not acceptable. To compound the difficulty an
ActiveX control in a Word document also lacks AfterUpdate and BeforeUpdate
events.
One effective approach to this problem is to use a similar method as with form field
controls. Where as "Enter" and "Exit" macros can be assigned to form fields; the
GotFocus and LostFocus events can perform the same function with ActiveX controls.
Note: extremely careful when testing with active GetFocus and LostFocus events. Be sure
to save all your work in Word before doing anything that could cause these to fire, as you
could end up in an endless loop. If the focus starts to bounce back and forth between
controls, triggering these two events in succession, Word will appear to freeze. Part of
the problem is that GetFocus and LostFocus are not aware of each other: the one won't
wait for the other to finish. You have to be very careful how code is constructed that uses
these for data validation and navigation, and test thoroughly.
But, because they're also integrated into and interface with a document's VBA segment
their code interface is in the ThisDocument module of the document they can be
manipulated just as any other object in the document. This is especially useful when
automating a document from another application. Listing 5 shows how you can get the
text value of an ActiveX control in a document, using VBA from any other application, or
Visual Basic.
Listing 5. Automate Word to get the value from an ActiveX control in a document.
Sub GetActiveXValueFromWordDoc()
Dim wdApp as Word.Application
Dim doc As Word.Document
For controls within the same document where the code is executing, you can simply use
the control name, as it is part of ThisDocument.
Why do the GotFocus events in the sample document use the first method, in Listing 4?
Because then it's not necessary to name the control explicitly in the code;
Selection.InlineShapes(1).OLEFormat.Object will pick up the ActiveX control
the user is entering. It can be copied without changes to the GotFocus event of every
control.
Data validation
The GotFocus event in Listing 4 passes the document containing the ActiveX control
and the control to the function ValidateData, in Listing 6.
The value of the document variable DataValidation is checked. If it contains the string
"True", then the control that was just exited contains valid content. In this case, the value
of the document variable PreviousControl is set to the name of the control just entered,
and this control is passed to the function SetControlContent.
Listing 6.
Public Function ValidateData(doc As Word.Document, o_Control As Object)
On Error Resume Next
If doc.Variables("DataValidation").Value = "True" Then
doc.Variables("PreviousControl").Value = o_Control.Name
SelectControlContent o_Control
Else
doc.Bookmarks(doc.Variables( _
"PreviousControl")).Range.InlineShapes(1).Select
End If
End Function
Public Function SelectControlContent(ctl As Object)
Dim lTextLen As Long
lTextLen = Len(ctl.Text)
ctl.SelStart = 0
ctl.SelLength = lTextLen
End Function
SetControlContent selects the text in a text box control. Why is this needed? Because
the GotFocus event apparently prevents the setting
frmEnterFieldBehaviorSelectAll of the EnterFieldBehavior property from
doing its job reliably. Sometimes it will work; sometimes not. The SetControlContent
function ensures that the entire content of the field being moved into is selected.
If the content of the control that was just exited is not valid, the focus is moved back into
it. It is identified using the value of the document variable PreviousControl, which also
represents a bookmark in the document. Unlike form fields, ActiveX controls do not
automatically create a bookmark in the document; these bookmarks were inserted by
selecting each ActiveX control and entering the bookmark name in Insert/Bookmark.
Even when the document is protected as a form, VBA can identify a bookmark range and,
since the ActiveX control is considered unprotected, select it.
How and when is the values of the document variable and DataValidation set? During the
LostFocus event of the control that was just exited. As before, the document containing
the control and the control are passed to a function, in this case SetDataValidation.
(See Listing 7.)
In this simplified example, the only criterion a control has to meet is that it may not be
left empty. If it is empty, the value of DataValidation is set to False. This causes the
ValidateData function (Listing 6) to return to this control.
Listing 7. The data is validated when exiting the control.
Private Sub txtAddress_LostFocus()
Dim doc As Word.Document
Set doc = ActiveDocument
SetDataValidation doc, txtAddress
'Display entire text content of the control, not just the last line
txtAddress.SelStart = 0
AdjustControlHeight txtAddress
txtAddress.SelStart = 0
End Sub
Public Function SetDataValidation(doc As Word.Document, _
o_Control As Object)
If Len(o_Control.Text) = 0 Then
doc.Variables("DataValidation") = "False"
Else
doc.Variables("DataValidation") = "True"
End If
End Function
What about fields that do not need to be validated, such as cboCountry? In this case, the
value of the document property DataValidation is simply set to "True", directly in the
LostFocus event.
Note: When the document is initialized (in the Document_Open event, for example), the
values of these document variables are reset to a default. This is especially important if
the document variables were never used before in the document. Document variables
cannot contain zero-length strings; they can't be "empty". If the content of a document
variable is deleted, the variable itself disappears.
Command button
The sample form contains a single command button control. Clicking it displays a
calendar control. As with other ActiveX controls in a protected form, a command button
should be formatted inline with the text, otherwise it may not be triggered by a mouse-
click. (It will, however, react to it accelerator key, if one has been specified in the
control's properties.)
If you need to position the command button with text flow, you can insert it into a Frame
(from the forms toolbar), just as with any control.
Often, you dont want a command button to appear when a form is printed. As long as it
is inline with the text, simply select it while in Design Mode and apply "hidden" font
formatting. Make sure the option to print hidden text is off in Tools/Options/Print. (Note
that you will probably still see it in Print Preview, but it should not print.)
Calendar control
The Professional versions of Office come with a calendar control, mscal.ocx, that can be
used in a Word document. Since this control was not made to work with Word
documents, it does not function correctly when located in a protected section of the
document. While you can use VBA code to manipulate it, and it can be clicked on, the
focus will generally jump to the next available, unprotected area. If you're using
GotFocus and LostFocus events, this can result in unpleasant loops that make it appear
as if Word has stopped responding.
So place a calendar control in an unprotected document section.
You may have noticed that this control is not visible in Figure 2. It's been formatted as
hidden, so that it won't show up on the print out, but that's not the entire story. The
control's height has also been set to the absolute minimum it will accept in the current
environment. The smallest acceptable value appears to depend on the version of Word. In
Word 2002 .5 points works; in Word 2000 .85 points. This makes the calendar essentially
invisible on-screen, especially if the display of non-printing characters is turned off.
When the user tabs into the txtDate form field in the body of the text, or clicks the
cmdDate command button, the procedure in Listing 8 is run, which increases the control's
height (in Word 2002 a height of 144 points seems optimal; in Word 2000 200 points). It
then appears on-screen as in Figure 3.
Listing 8. Increase the height of the calendar control to make it visible.
Public Sub DisplayCalendar()
ActiveDocument.cal.Height = 144
End Sub
Option buttons
A frequently asked question concerning option buttons is a document is how to make
them mutually exclusive. In a Userform, you have the option of inserting them into a
Frame control. This is not available in the Word document environment.
But if you assign the same name to option buttons' Group property, then those in the
same group will be mutually exclusive.
In versions of Word prior to 2002 you also need to be aware that option buttons don't
print properly: they will all print as if they hadn't been activated. This problem was
correct in Word 2002 and later versions.
In earlier versions of Word you'd need to use a macro named FilePrint (or in Word
2000 you could use the BeforePrint method) to overlay or replace the options buttons
with font symbols that represent the controls' actual status.
attached template, but any references to the controls that do not specify in which
document to look for them will appear not to work.
Compare how the code in Listing 10 passes the cboCountry control to the
PopulateList function with how the txtAddress control is passed in the LostFocus
event of Listing 7. In Listing 10, it's clear that the control in question is found in the
"ActiveDocument"; in Listing 7, the control is assumed to be in the same file
containing the code because it's not otherwise qualified.
So, you have the choice of
Specifying in your code in which document VBA should look for the control.
Having the template make a copy of an existing document, then open that.
(Re-)Using a document instead of a template. (You could display the File/Save As
dialog box to remind the user to save the document to a new name for a new form.)
The Document_New event in Listing 10 sets up the form. The combo box is populated,
any fields (dates, links to external files, etc.) in the document are updated, the document
variables for the data validation are initialized, and the first control is selected. In
addition, hidden text is displayed, while all other non-printing characters and bookmarks
are suppressed.
Listing 10. Prepare the form when a new document is created from a template.
Private Sub Document_New()
Dim doc As Word.Document
Set doc = ActiveDocument
doc.cboCountry.Clear
doc.Fields.Update
PopulateList doc.cboCountry
doc.Variables("PreviousControl").Value _
= doc.InlineShapes(1).OLEFormat.Object.Name
doc.Variables("DataValidation").Value = "False"
doc.InlineShapes(1).Select
With doc.ActiveWindow.View
.ShowHiddenText = True
.ShowAll = False
.ShowBookmarks = False
End With
End Sub