Do-It-Yourself IntelliSense
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
VBA Hacker
Part I: Adding Your Own "Replace as You Type" Magic
to Word
By Romke Soldaat
With the arrival of Microsoft's IntelliSense technology
in Office 95, software suddenly started to get brains. Word's smart
cut-and-paste feature guarantees that drag-and-drop operations don't mess
up your spaces and punctuation marks. AutoCorrect and AutoFormat are great
time savers, and on-the-fly spell checking is a godsend. On the other
hand, I consider AutoSummarize (introduced in Word 97) still too dumb; and
automatic language detection (Word 2000) too annoying to be useful.
Over the next few Office generations, the performance
and reliability of these IntelliSense features will undoubtedly increase.
But that doesn't mean that we have to wait for our friends in Redmond to
fulfill our wishes. In this three-part series, I'll introduce a few
methods and mechanics to add your own touch of IntelliSense to Word 95,
97, and 2000.
AutoFormat as You Type
Most IntelliSense features in Word can be turned on and
off in the AutoCorrect dialog box (accessible from the Tools menu). FIGURE
1 shows the options in Word 2000. The Replace as you type section in the
dialog box offers some really useful options, such as smart quotes,
superscripted ordinals, and automatic hyperlinks.
FIGURE 1: AutoCorrect features in Word 2000
On the other hand, the Fractions (1/2) with fraction
character (1/2) feature on the AutoFormat As You Type page is too limited.
It only replaces the character combinations 1/4, 1/2, and 3/4 with the
standard ANSI characters 1/4, 1/2, and 3/4. In Part II, I'll introduce a
way to auto format any fraction, e.g. typing 11/32 automatically becomes
11/32. First we need to look at the basics of
writing "intelligent" macros that "sense" what you're trying to type and
correct your text on the fly.
Word's IntelliSense features usually kick in when you
type a space, a bracket, or a punctuation mark. Word then looks at what
you just wrote, checks it against your AutoCorrect and AutoFormat
settings, and changes your text if appropriate. If automatic proofing is
enabled, Word also checks the spelling and grammar of your typing - all at
the speed of light.
Being a fast, compiled application, Word has all the
time it needs to look over your shoulder and watch what you're doing. In a
VBA application, however, you don't have that luxury. As far as I can
tell, there's no workable method to monitor Word continuously and execute
a command or macro when a certain string is typed. But there's another
way.
You probably know that you can assign a macro, command,
style, AutoText entry, font, or symbol to a shortcut key or key
combination of your choice using the Add method of the
KeyBindings collection. For example, the following code assigns the
Arial font to the [Ctrl][Shift][A] key combination:
KeyBindings.Add KeyCategory:=wdKeyCategoryFont, _
Command:="Arial", KeyCode:= _
BuildKeyCode(wdKeyControl, wdKeyShift, wdKeyA)
This instruction uses the BuildKeyCode method to
create a numeric value by adding one, two, or three values, which are
categorized in the Object Browser under the wdKey list of
constants. Normally, you would use this option with key combinations that
involve the [Alt], [Ctrl], and/or [Shift] modifier key, but nothing
forbids you from linking a macro with an unmodified key, such as the key
that produces the letter "f" or the slash character. For example, the
following code assigns the lowercase "f" key to the Intercept_F
macro:
KeyBindings.Add KeyCode:=wdKeyF,_
KeyCategory:=wdKeyCategoryMacro, _
Command:="Intercept_F"
Here, wdKeyF (decimal value 70) is one of the
wdKey constants that you can look up in the Object Browser. Because
the constant values for alphabetical characters in the range a-z and A-Z
are always the same, regardless of the keyboard layout, it's safe to use
them. Later in this article, you'll find out that, for many other keyboard
characters, it's dangerous to rely on wdKey constants, and you'll
have to use another way to get the correct key codes.
First Exercise: AutoDegrees
Let's assume you want to automate the formatting of
Fahrenheit and Celsius (Centigrade) degrees. You decide that typing the
uppercase letter F or C immediately after one or more numeric characters
should insert the degrees character (° - ANSI value 176) between the
numerals and the F and C characters. So, when you type 125F, Word
automatically changes your typing into 125°F, and 90C becomes 90°C. What
you need is one macro that's triggered by typing the letter F and another
one that runs when you type the letter C. The routine in FIGURE 2 creates
these keyboard assignments.
Sub ActivateAutoDegrees()
' Define where the customization is stored.
CustomizationContext = NormalTemplate
' Assign the uppercase F character.
KeyBindings.Add _
KeyCategory:=wdKeyCategoryMacro,_
Command:="Intercept_F", _
KeyCode:=BuildKeyCode(wdKeyShift, wdKeyF)
' Assign the uppercase C character.
KeyBindings.Add _
KeyCategory:=wdKeyCategoryMacro,_
Command:="Intercept_C", _
KeyCode:=BuildKeyCode(wdKeyShift, wdKeyC)
End Sub
FIGURE 2: Assigning the uppercase C and F characters to
macros
Once you've executed this macro, typing the uppercase
letter F triggers the Intercept_F macro, and the letter
C runs the Intercept_C macro. Both macros do essentially the same
job; FIGURE 3 shows the listing of the macro that intercepts the F key.
Sub Intercept_F()
On Error Resume Next
With Selection
' If the selection isn't an insertion point,
' type the letter F and jump out.
If . Type <> wdSelectionIP Then
.TypeText "F"
Exit Sub
End If
' Create a range object for the current selection.
Dim TextBeforeIP As Range
Set TextBeforeIP = .Range
' Extend the start position of the range to the
' beginning of the word before the insertion point.
TextBeforeIP.StartOf Unit:=wdWord, Extend:=wdExtend
' If the entire word consists of digits and does not
' end with a space, treat it as an AutoDegree
' candidate.
If IsNumeric(TextBeforeIP.Text) And _
Right(TextBeforeIP.Text, 1) <> " " Then
.TypeText "ºF"
Else
.TypeText "F"
End If
End With
End Sub
FIGURE 3: This macro runs when the letter F is typed.
Note the use of the Selection
and Range
objects.
The With block in this macro contains the code
that works on the current selection. First, the macro checks to see
whether the selection is something other than an insertion point. If
that's the case, it assumes you want to replace the selected text with the
letter F (which is done with the TypeText method), and the macro
exits. Otherwise, a Range object with the name TextBeforeIP
is created for the insertion point. The StartOf method is used to
extend the start position of that range to the beginning of the word at
the left of the insertion point. At this point, the TextBeforeIP range contains the
last typed word. If this word can be evaluated as a number (using the IsNumeric function) and doesn't
end with a space character (meaning that the letter F wasn't intended to
be typed immediately after the number), the macro inserts the string °F,
otherwise the letter F is typed.
Problems!
Although the AutoDegree macros will work properly in
most cases, there are a few potential problems. Because you have redefined
the behavior of the uppercase C and F characters, you'll end up with
situations where typing these characters doesn't have the desired effect.
One example is when you're typing text in the header of
an e-mail message. If you type an uppercase C or F in either the To:, Cc:,
or Subject: fields, the AutoDegree macros will generate error 4605: "This
method or property is not available because the current selection is in
the mail header." The version of the macros in Listing
One intercepts this error and prompts you to use the [CapsLock] key to
type an uppercase C or F. It's not an elegant solution, but it's workable.
The blocked [C] and [F] keys are also incompatible with
some of Word's dialog boxes. There's no problem with dialog boxes in which
you type unformatted text, such as the Open, Properties, and Find dialog
boxes; both characters can be typed without a hitch. But some other dialog
boxes contain so-called "rich" textboxes where you can bold, italicize,
etc. the text. The ones I found are the dialog boxes associated with the
Caption command on the Insert menu, and the Envelopes and Labels command
on the Tools menu. Typing an uppercase C or F in these dialog boxes is
impossible, unless you use the [CapsLock] key.
The problem is that you can't trap these occurrences
programmatically; macros simply don't run while dialog boxes are
displayed. So, the only way to tackle this problem is by resetting the [C]
and [F] keys to their default behavior before displaying the dialog box
and reassigning them to the AutoDegrees macros afterwards. But how?
Rewriting Commands
Ever since the good (or bad?) old days of WordBasic, you
were able to replace a built-in command by writing a macro with the same
name. But how do you find out which command displays a certain dialog box?
In pre-2000 versions of Word, you had to guess. Usually you can combine
the name of the menu that contains the command with the name of the
command itself. For example, the Save As command on the File menu is
linked with the FileSaveAs
macro. Sometimes you may have to fiddle a bit with the concatenation of
the menu and command name; for example, the command that inserts the Date
and Time isn't called InsertDateAndTime, but InsertDateTime.
If you have Word 2000, your life is much easier thanks
to the new CommandName property of the Dialogs object, which
returns the name of the procedure that displays a specified built-in
dialog box. The following instruction assigns the string FileSaveAs
to the MacroName variable:
MacroName = Dialogs(wdDialogFileSaveAs).CommandName
This way you'll discover that the commands associated
with the Caption, Envelopes, and Labels dialog boxes are
InsertCaption and ToolsEnvelopesAndLabels, respectively. All
you have to do is write macros with the same names that temporarily
disable the keyboard assignments, display the appropriate dialog box, and
then recreate the key bindings. Listing
Two contains these macros.
Second Exercise: AutoMetrics
So far, so good, but there are more clouds on the
horizon. Let's assume you also want an automatic correction of metric
expressions. This may not be very relevant for most US readers, but the
rest of the world abolished the imperial system a long time ago. (There's
a scattered probe on Mars that illustrates this conflict rather
painfully.) An AutoMetrics application should be able to convert strings
like km1, m2, and dm3 into km¹, m², and dm³, respectively. (The
superscripted characters have the ASCII values 185, 178, and 179,
respectively.)
The "intelligence" in the macros will be very much the
same as in the AutoDegrees project; all they have to do is look for a
metric unit (the options are mm, cm, dm, m, dam, hm, and km), preceded by
a numeric expression or a space. If that's the case, the superscripted
numeral is inserted; otherwise the standard size numeral is used. FIGURE 4
shows a listing that converts the character 2 into a metric square symbol
(²) if the last-typed text includes a metric unit. You'll see many
similarities to the listing in FIGURE 3.
Sub Intercept_2()
On Error Resume Next
With Selection
' If the selection isn't an insertion point,
' type the appropriate digit.
If . Type <> wdSelectionIP Then
.TypeText "2"
Else
' Create a range object for the current selection.
Dim TextBeforeIP As Range
Set TextBeforeIP = .Range
With TextBeforeIP
' Extend the start position of the range to the
' beginning of the word before the insertion point.
.MoveStart Unit:=wdWord, Count:=-1
' If the first character is a digit, move the start
' of the range one character at a time to the right,
' until the first character is NOT a digit.
If IsNumeric(.Characters.First) Then
While IsNumeric(.Characters.First)
.MoveStart Unit:=wdCharacter, Count:=1
Wend
End If
' See what text is now in the range.
Select Case LCase(.Text)
Case "mm", "cm", "dm", "m", "dam", "hm", "km"
' A valid metric unit, so we type the
' superscripted digit.
Selection.TypeText "²"
Case Else
' Not an AutoMetric candidate.
Selection.TypeText "2"
End Select
End With
End If
End With
If Err.Number = 4605 Then
MsgBox "Use the numeric keypad to type a 2"
End If
End Sub
FIGURE 4: This macro runs when the number 2 is typed.
The Range
object is extensively used to analyze the text just
before the insertion point.
More Problems!
The AutoMetrics macros are designed to be associated
with the keys that create the characters 1, 2, and 3 on the standard
keyboard. Because these key assignments have the same drawbacks as the
ones for the AutoDegrees macros (i.e. they fail to work in an e-mail
header or rich-text dialog box), I decided to leave the numeric keypad
untouched. This way we keep the option available to use the keypad keys
(or the virtual keypad on a portable PC) to type a 1, 2, or 3 in dialog
boxes that are incompatible with our macros. Listing
Three contains the AutoMetrics macros.
But there's another problem, and this time it's
Microsoft's fault. If you didn't know better, you would probably use the
following code to assign a macro to the [1] key:
KeyBindings.Add KeyCode:=wdKey1,_
KeyCategory:=wdKeyCategoryMacro, _
Command:="Intercept_1"
The wdKey1 constant has a numeric value of 49,
which is indeed the code for the [1] key on many keyboards, including the
US and UK versions. But on French, Belgian, and some Eastern European
keyboards, all numeric characters are on shifted keys, so you have to type
[Shift][1] to produce the "1" character. As a result, running the previous
code will not assign the Intercept_1 macro to the [1] key, but to
the ampersand (&) character on a French keyboard, and to the plus sign
(+) on a Czech keyboard!
The KeyBinding Mess
Don't think this is an isolated case. If your keyboard
(or that of you customers) doesn't have a US layout, you'll find out that
the wdKey list of constants in the Object Browser is not only
misleading, but also dangerous. The list uses constant names that are
supposed to be easy to remember - which they are in many cases. You can
safely assume that wdKeyA always contains the value associated with
the "a" character (lowercase, not uppercase!) on any keyboard that
supports the Latin alphabet. The same applies to the wdKeySpacebar,
wdKeyNumeric5, and wdKeyF10 constants, to name a few. All
keyboard drivers use identical "scan codes" for function keys, cursor
keys, keys on the numeric keypad, and most alpha keys, even if their
physical position on the keyboard isn't the same. But that's where it
stops.
Take for example the / key. You would expect that you
could use the wdKeyControl (512) and wdKeySlash (191)
constants to create a [Ctrl]/ key binding:
KeyBindings.Add KeyCategory:=wdKeyCategoryCommand, _
Command:="WindowSplitWindow",_
KeyCode:=BuildKeyCode(wdKeyControl, wdKeySlash)
This will work on a PC with a US or UK keyboard, but
fails miserably on many other configurations. Why? Because the / key on
many international keyboards doesn't have 191 as the scan code. For
example, on a Dutch keyboard, the slash character has a scan code of 219,
which happens to be the value of the wdKeyOpenSquareBrace constant,
which represents the "[" character. How about that for mnemonic constant
names?
I found at least eight keyboards that have different
scan codes for the / key. On each of them, the above instruction will
assign the command to the key combination you do not want, and - worse -
overwrite any existing key binding without warning!
Microsoft doesn't say a word about this in VBA Help, and
I haven't seen a single VBA book that discusses this problem. So, if you
want to write applications that must run on any keyboard, you should avoid
the use of wdKey constants like the plague, and find another way to
obtain keycodes.
Get the API to Do the Work VBA Can't Do
Whenever VBA fails to behave the way you expect or want
it to, it pays to see if the Windows API does a better job. In
the case of solving the problem of unreliable wdKey constants,
solace comes from the Windows VkKeyScan function. This function
must be declared as follows:
Public Declare Function VkKeyScan Lib "user32" _
Alias "VkKeyScanA" (ByVal cChar As Byte) As Integer
VkKeyScan translates an ANSI character into a
virtual-key code plus a shift state for the current keyboard. The function
requires one parameter, cChar, which must be a Byte value, an 8-bit
number ranging in value from 0-255. The return value is the sum of the
virtual-key code and the shift state (256 for the [Shift] key, 512 for the
[Ctrl] key, and 1024 for the [Alt] key).
You can obtain the cChar value for a specific
ANSI character using the VBA Asc function. For example, to find the
key code of the [1] key, regardless of the current keyboard layout, use
the following syntax:
OneKey = VkKeyScan(Asc("1"))
On most keyboards, this will return a value of 49 (the
same as the wdKey1 constant), but on a French, Czech, or Slovak
keyboard, you'll get 305, which is the sum of the wdKeyShift (256)
and wdKey1 constants. The following code snippet uses
VkKeyScan rather than the wdKey1 constant to assign the
Intercept_1 macro to the [1] key:
KeyBindings.Add _
KeyCode:= VkKeyScan(Asc("1")), _
KeyCategory:=wdKeyCategoryMacro, _
Command:="Intercept_1"
FIGURE 5 shows a generic AssignMacroToCharacter
routine that calls the Windows VkKeyScan function to create a
foolproof link between any macro and any keyboard character, as well as a
ResetCharacter macro that removes the key binding.
Sub AssignMacroToCharacter( _
MacroName As String, Character As String)
CustomizationContext = NormalTemplate
KeyBindings.Add _
KeyCategory:=wdKeyCategoryMacro,_
Command:=MacroName, _
KeyCode:=VkKeyScan(Asc(Character))
End Sub
Sub ResetCharacter(Character As String)
CustomizationContext = NormalTemplate
FindKey(VkKeyScan(Asc(Character))).Disable
End Sub
FIGURE 5: (Top) Assigning a macro to a keyboard
character. (Bottom) Resetting the default behavior of the character. The
Windows VkKeyScan
function returns the correct scan code for a
specified character on any keyboard.
The syntax is:
' Create key binding.
AssignMacroToCharacter "Intercept_1", "1"
' Delete key binding.
ResetCharacter "1"
The listings at the end of this article use similar
instructions in the AutoMetrics and AutoDegrees modules. Together with the
IntelliLib module (which contains generic code and some API declarations),
these modules form a complete project that adds two new IntelliSense
features to Word.
A Better KeyString Function
Because of the same US bias that makes the wdKey
constants unusable, you can't rely on VBA's KeyString function and
method either. This function is designed to return a string that
identifies a certain key combination. For example:
MsgBox KeyString(BuildKeyCode(wdKeyControl, _
wdKeyShift, wdKeyA))
displays the string Ctrl+Shift+A. Again, with standard
alphabetical characters, this works fine. But as soon as you go beyond the
A-Z range, you can't rely on KeyString at all. For example:
KeyString(BuildKeyCode(wdKeyShift, wdKey6))
always returns the circumflex character (^), even though
it should return a slash character (/) on a Hungarian keyboard, a question
mark (?) on a Canadian multilingual keyboard, and an ampersand (&) on
a Danish or German keyboard! An unforgivable blunder.
Thankfully, the Windows API is also helpful in finding
the correct key string associated with a specified character. This trick
requires two additional functions: MapVirtualKey and
GetKeyNameText. The MapVirtualKey function translates (among
other things) a virtual-key code into a scan code. The
GetKeyNameText function retrieves a string that represents the name
of a key. I won't discuss the details of these functions here; you can
read more about them in the MSDN library, or in any book that deals with
the Windows API.
In the IntelliLib module (again, refer to Listing Two),
you'll find a GetKeyString function that tells you which key
combination must be used to generate a certain character. Unlike VBA's
KeyString function, this GetKeyString function is
keyboard-aware, and will return the correct key combination for any ANSI
character on any hardware configuration. For example, to find out which
keys need to be used to generate the @ character, use:
KeyText = GetKeyString("@")
Depending on the current keyboard, this will give you
the key strings shown in the table in FIGURE 6.
|
Keyboard |
Key string |
|
US |
Shift+2 |
|
UK |
Shift+' |
|
French |
Alt+Ctrl+0 |
|
Dutch |
@ |
|
German |
Alt+Ctrl+Q |
FIGURE 6: Key strings used for the @ character
There's no way you can get this information in VBA!
Conclusion
The purpose of this series is to demonstrate that
there's a lot more to VBA programming than you may think. With a bit of
hacking, you can create powerful new tools to enhance your Office
environment. In this first of three articles, I demonstrate how you can
extend Word's IntelliSense features with an AutoDegrees and AutoMetrics
feature, and I pointed you at a way to solve a serious problem with Word's
keycode constants.
In the next installment, we'll build an application that
beats Word's AutoFractions by miles, and a nifty AutoCalculate
function that converts any number or calculation automatically in a choice
of different formats. The third part of this series will introduce a
powerful tool as an alternative for Word's limited and complicated ways to
create accented international characters.
The VBA source referenced in this article is
available for download.
This file contains a ready-to-use and unprotected Word template with the
code for all three installments of this series. The README.TXT file in the
zip contains full installation instructions.
Dutchman Romke Soldaat was hired by Microsoft in 1988
to co-found the Microsoft International Product Group in Dublin, Ireland.
That same year he started working with the prototypes of WinWord, writing
his first macros long before the rest of the world. In 1992 he left
Microsoft, and created a number of successful add-ons for Office. Living
in Italy, he divides his time between writing articles for this magazine,
enjoying the Mediterranean climate, and steering his Land Rover through
the world's most deserted areas. Romke can be contacted at romke@soldaat.com.
Begin Listing One -
AutoDegrees.bas
Sub ActivateAutoDegrees()
' Define where the customization is stored.
CustomizationContext = NormalTemplate
' Assign the uppercase F character.
AssignMacroToCharacter "Intercept_F", "F"
' Assign the uppercase C character.
AssignMacroToCharacter "Intercept_C", "C"
StatusBar = "AutoDegrees Activated"
End Sub
Sub DeactivateAutoDegrees()
' Define where the customization is stored.
CustomizationContext = NormalTemplate
' Reset the default behavior of the keys.
ResetCharacter "F"
ResetCharacter "C"
StatusBar = "AutoDegrees deactivated"
End Sub
Sub Intercept_C()
FormatAutoDegree "C"
End Sub
Sub Intercept_F()
FormatAutoDegree "F"
End Sub
Sub FormatAutoDegree(DegreeSymbol As String)
On Error Resume Next
With Selection
' If the selection isn't an insertion point,
' type the letter C or F and jump out.
If . Type <> wdSelectionIP Then
.TypeText DegreeSymbol
Else
' Create a Range object for the current selection.
Dim TextBeforeIP As Range
Set TextBeforeIP = .Range
' Extend the start position of the range to the
' beginning of the word before the insertion point.
TextBeforeIP.StartOf Unit:=wdWord, Extend:=wdExtend
' If the entire word consists of digits and doesn't
' end with a space, treat it as an AutoDegree
' candidate.
If IsNumeric(TextBeforeIP.Text) And _
Right(TextBeforeIP.Text, 1) <> " " Then
.TypeText "º" & DegreeSymbol
Else
.TypeText DegreeSymbol
End If
End If
End With
If Err.Number = 4605 Then
MsgBox _
"Use the CapsLock key to type an uppercase C or F"
End If
End Sub
End Listing One
Begin Listing Two -
IntelliLib.bas
Public Declare Function VkKeyScan Lib "user32" _
Alias "VkKeyScanA" (ByVal cChar As Byte) As Integer
Public Declare Function GetKeyNameText Lib "user32" _
Alias "GetKeyNameTextA" (ByVal lParam As Long,_
ByVal lpBuffer As String, ByVal nSize As Long) As Long
Public Declare Function MapVirtualKey Lib "user32" _
Alias "MapVirtualKeyA" (ByVal wCode As Long,_
ByVal wMapType As Long) As Long
Sub AutoExec()
ActivateAutoDegrees
ActivateAutoMetrics
End Sub
Sub AutoExit()
DeactivateAutoDegrees
DeactivateAutoMetrics
End Sub
Sub AssignMacroToCharacter(MacroName As String, _
Character As String, Optional ShiftState As Integer)
' ShiftState values.
' Shift=256.
' Ctrl=512.
' Alt=1024.
CustomizationContext = NormalTemplate
KeyBindings.Add _
KeyCategory:=wdKeyCategoryMacro,_
Command:=MacroName,_
KeyCode:=VkKeyScan(Asc(Character)) + ShiftState
End Sub
Sub ResetCharacter(Character As String, _
Optional ShiftState As Integer)
CustomizationContext = NormalTemplate
FindKey(VkKeyScan(Asc(Character)) + ShiftState).Disable
End Sub
Sub InsertCaption()
' Disable the key assignments.
DeactivateAutoDegrees
DeactivateAutoMetrics
' Display the dialog box.
Dialogs(wdDialogInsertCaption).Show
' Re-enable the key assignments.
ActivateAutoDegrees
ActivateAutoMetrics
End Sub
Sub ToolsEnvelopesAndLabels()
' Disable the key assignments.
DeactivateAutoDegrees
DeactivateAutoMetrics
' Display the dialog box.
With Dialogs(wdDialogToolsEnvelopesAndLabels)
' Mimick the behavior of the original command.
.ExtractAddress = True
.Show
End With
' Re-enable the key assignments.
ActivateAutoDegrees
ActivateAutoMetrics
End Sub
Function GetKeyString(Char As String)
Dim KeyName As String * 256
Dim ShiftState As String
Dim KeyCode As Long, ScanCode As Long, lReturn As Long
' VkKeyScan returns the scan code for the character.
KeyCode = VkKeyScan(Asc(Char))
' Look at the keycode part that identifies the shift
' state, using VBA constants for the Alt, Ctrl,
' and Shift key.
ShiftState = _
IIf(KeyCode And wdKeyAlt, "Alt+", vbNullString) & _
IIf(KeyCode And wdKeyControl, "Ctrl+", vbNullString) & _
IIf(KeyCode And wdKeyShift, "Shift+", vbNullString)
' Translate the virtual-key code into a scan code.
ScanCode = MapVirtualKey(KeyCode, 0)
' GetKeyNameText retrieves the name of a key.
' The return value is the lengh of the string.
lReturn = GetKeyNameText(ScanCode * 2 ^ 16, KeyName, 255)
GetKeyString = ShiftState & Left(KeyName, lReturn)
End Function
End Listing Two
Begin Listing Three -
AutoMetrics.bas
Sub ActivateAutoMetrics()
' Define where the customization is stored.
CustomizationContext = NormalTemplate
AssignMacroToCharacter "Intercept_1", "1"
AssignMacroToCharacter "Intercept_2", "2"
AssignMacroToCharacter "Intercept_3", "3"
StatusBar = "AutoMetrics Activated"
End Sub
Sub DeactivateAutoMetrics()
' Define where the customization is stored.
CustomizationContext = NormalTemplate
' Reset the default behavior of the keys.
ResetCharacter "1"
ResetCharacter "2"
ResetCharacter "3"
StatusBar = "AutoMetrics deactivated"
End Sub
Sub Intercept_1()
FormatAutoMetrics 1
End Sub
Sub Intercept_2()
FormatAutoMetrics 2
End Sub
Sub Intercept_3()
FormatAutoMetrics 3
End Sub
Sub FormatAutoMetrics(ByVal MetricsSymbol As Integer)
On Error Resume Next
With Selection
' If the selection isn't an insertion point,
' type the appropriate digit.
If . Type <> wdSelectionIP Then
.TypeText CStr(MetricsSymbol)
Else
' Create a range object for the current selection.
Dim TextBeforeIP As Range
Set TextBeforeIP = .Range
With TextBeforeIP
' Extend the start position of the range to the
' beginning of the word before the insertion point.
.MoveStart Unit:=wdWord, Count:=-1
' If the first character is a digit, move the start
' of the range one character at a time to the
' right, until the first character is NOT a digit.
If IsNumeric(.Characters.First) Then
While IsNumeric(.Characters.First)
.MoveStart Unit:=wdCharacter, Count:=1
Wend
End If
' See what text is now in the range.
Select Case LCase(.Text)
Case "mm", "cm", "dm", "m", "dam", "hm", "km"
' A valid metric unit, so we type the
' superscripted digit.
Selection.TypeText _
Chr(Choose(MetricsSymbol, 185, 178, 179))
Case Else
' Not an AutoMetric candidate.
Selection.TypeText CStr(MetricsSymbol)
End Select
End With
End If
End With
If Err.Number = 4605 Then
MsgBox "Use the numeric keypad to type a 1, 2 or 3"
End If
End Sub
End Listing Three