Heading

This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
min read

Get keycode function improved

Word • Macros • Functions • Organization
Peter Ronhovde
48
min read

As we expand our library of editing macros, we inevitably need a way to automatically reassign keyboard shortcuts. Adding new keyboard shortcuts is a little messy in Word VBA, so this function provides a concise and efficient approach to determine the unique keycode for any valid keyboard shortcut combination. Additional error checks and enhancements are also included.

Thanks for your interest

This content is part of a paid plan.

Improved get keycode function

Assigning keyboard shortcuts to macros or commands is called “keybinding.” As seen in a previous introductory article, adding a new keybinding is clumsy in Word VBA. Depending on how we structure our simplified keyboard shortcut assignments, it helps to create a few functions to break the process down into smaller bite-sized chunks. One of those pieces determines the unique “keycode” for whatever key combination is specified. Fortunately, creating this initial function accomplishes the bulk of the work necessary to streamline and automate our own keyboard shortcut assignments.

This article is part of a series of mostly member articles to automatically assign our own keyboard shortcuts in VBA. Each article covers different aspects of the small project.

The introductory portion of this article has a significant overlap with an earlier version, but this version has a few advantages including efficiency improvements, error checking, and allowing all four modifier keys in Word for Mac.

Safety locks

You know how we put safety locks on some cabinets until the kids get older?

Well … let’s take a safety lock off and see what happens.

What’s the problem?

The current function is more concise than the previous version and also adds a few logical error checks, but we need to tinker a little bit with the inner workings of how a keybinding keycode works. Fortunately, it’s simple and mostly safe.

The more we tinker around with something, taking it apart while assuming we know how it works, the more likely something will ruin our day down the road. For one, we’re assuming the keycode calculation won’t ever change which comes with some risk. In practice it probably won’t, but if you prefer a more direct even if verbose solution using the standard BuildKeyCode function, see the previous article.

Modifier keys

What are modifier keys?

You’ve tapped them thousands of times and probably not called them that. Modifier keys allow us to change the action associated with another key. Together, we call it a “keyboard shortcut” with a classic example being Control+Z (or Command+Z on a Mac) to Undo the previous action.

The application must be set up to interpret the intended action for any keyboard shortcut. Word and the operating system come with a default set of shortcuts. We also have some control over assigning our own macros to custom keyboard shortcuts, and we can do so automatically in Word through VBA.

Additional considerations

Some key combinations are not recognized by Word’s custom keybinding methods, but the specifics vary between Word for Windows or Mac. For example, punctuation or numpad keys may not be interpreted as intended.

The Shift key usually needs to be combined with at least one other modifier key because it has pre-defined actions with most other keys such as alphabetic capitalization, accessing alternative characters, or entering a selection mode when combined with movement keys.

One of the error checks in this function attempts to catch any issues with some poorly selected shortcut combinations. In practice, we should validate any unusual shortcuts before automatically assigning them in a VBA macro.

Windows

In Word for Windows, the available modifier keys are Control, Alt, and Shift.

No Windows command key in Word

We cannot use the Windows Command key as a modifier key in Word. There are some workarounds to use it at the operation system level using apps such as PowerToys or AutoHotkey, but both are beyond the scope of this article.

Mac

Macs have four (really five) modifier keys including Command, Control, Option, and Shift.

All four modifier keys work

We manually calculate the shortcut keycode in this function, so we can use any combination of these keys up to all four of them. The BuildKeyCode function used the typical VBA keybinding assignments does not allow this enhancement.

Mostly no Function (Globe) key use

At the OS level, Macs also have the “Function” (Globe) key which is different from the “Function Keys” along the top of most keyboards. How’s that for clarity?

The Globe key can’t be used to assign any keyboard shortcuts inside Word, but they can be set to access menu commands or insert special characters. Use the Mac system settings to make any of these changes.

What about no modifier keys?

What keyboard shortcuts work without any modifier keys?

We can assign custom keyboard shortcuts without modifier keys to many keys, but tread with caution. Overriding fundamental editing keys may make your keyboard work unintuitively, so limiting such custom keyboard shortcuts to certain special keys is strongly advised. Examples include the function keys, arrow keys, Insert, Delete, Page Up, Page Down, Home, End, or maybe a few others. The function keys are also assigned the specific actions in Word but nothing as fundamental to word processing as the others. Overriding other keys alone should probably only be done in the context of improving existing actions.

I override many function keys with some of my own macros because I don’t use most of Word’s default assignments. For example, I assigned Control+Shift+F7 (or Command+Shift+F7 in Word for Mac) to automatically correct the previous misspelled word on the current page. I am tempted to override Page Up and Page Down since I improved the actions with my own macros, but I don’t use the others unless I am binding it with at least one modifier key.

Function skeleton

An important utility function for assigning keyboard shortcuts in Word VBA is getting the unique keycode for a given combination of shortcut keys. The function skeleton is:

Function GetKeyCode(ByVal sModifierKeys As String, MainKey As Long) As Long
' Return the unique keycode for assigning a shortcut keybinding
' MainKey should be an appropriate WdKey enumeration value or an arrow key constant
' from the VBA keycode table

End Function

We’ll end up creating two versions for Windows and Mac since the steps are different enough to be a little bit of a problem, but there is a combined version in the extended content if you’re willing to dig through using preprocessor directives.

Parameters

The function requires two arguments from the user.

I like to precede plain text String variables with an “s” or “S” to identify the variable type to myself. A capital first letter implies the variable value will not change inside the function. VBA does not care nor enforce either “rule” provided we don’t try to use one of its reserved words for a variable name.

Modifier keys text string

The first parameter requires a plain text string telling the macro what the modifier keys are for the keyboard shortcut. We imagine something like “Control+Shift”. We’ll call this variable sModifierKeys.

One of the first function steps will convert all alphabetic characters in sModifierKeys to lowercase. Any changes to String arguments in VBA can leak back outside the function, so the ByVal keyword tells VBA not to change the argument value outside the function.

In this function, the “main key” such as “A” or “9” does not need to be included in the sModifierKeys text. As far as this function is concerned, plain text values of “Control+Shift” and “Control+Shift+A” will give the same keycode function result provided the next MainKey parameter is specified correctly. Any “+A” part of the keyboard shortcut text will be ignored by the steps that process the sModifierKeys variable. An upcoming article in this series will allow us to interpret a keyboard shortcut assignment in this more convenient pattern “Control+Shift+A”.

Main key

The second parameter requires a standard Word key constant as a Long number type (essentially a larger Integer in VBA). We’ll call it MainKey.

I would prefer to define the variable as part of the WdKey enumeration, but unfortunately, arrow keys do not appear in the table but are instead defined in the Keycode constants table. Another problem is VBA does not constrain any number assignments to values within the tables anyway (some other languages do), so we would need to do our own error checks anyhow. In VBA, enumerations are mostly an organizational convenience and to help with reading common constant values.

We can still use WdKey constant values with our MainKey parameter since Word automatically converts them to Long number types when needed. The WdKey table includes most special keys like Home or Delete as well as all text characters. For example, we could specify an “A” key by passing the function a value of wdKeyA.

Return value

The function returns a Long value because a KeyCode property we’ll cover in a later article requires a unique identifier as a Long number type.

Interpret the modifier keys

In Word VBA, every possible key combination has a unique value which we use to identify the keyboard shortcut assigned to a macro or Word command. We need to interpret the specific key combination for the keyboard shortcut based on the sModifierKeys text provided by the user and calculate its keycode.

Define keycode number variable

We’ll start by defining a numeric variable myKeyCode which will temporarily store the unique keycode number as a Long number type.

Dim myKeyCode As Long

We’ll eventually use this value to assign the return value, but it also provides a working variable for our keycode calculation until then.

How BuildKeyCode works

We should understand more about what’s going on under the hood with the keycode stuff. Fortunately, it’s simple. The BuildKeyCode function was used in the previous introductory article and is used in most Microsoft documentation examples. For example, we might use the BuildKeyCode function as follows. We'll pretend to store the result in the myKeyCode variable.

myKeyCode = BuildKeyCode(wdKeyControl, wdKeyShift, wdKeyA)

It looks good as a function with a clear name indicating its task, but it sounds more mysterious than it is. Here is what BuildKeyCode actually does.

' BuildKeyCode function just adds key constants
myKeyCode = wdKeyControl + wdKeyShift + wdKeyA

That’s all? Yeah, it’s kind of underwhelming.

Why does this matter?

Under the hood, it just adds the key values together, so we can simplify our previous GetKeyCode function depending on which modifier keys we detect if we’re willing to get our hands dirty with a little confidence. Our GetKeyCode function becomes more concise and a little more general at the same time.

Starting keycode value

We start with a keycode value of zero, so we can sum the detected modifier keys.

myKeyCode = 0 ' Initialize key code value to no modifier keys

This starting value also provides a way to detect when no modifier keys have been given since it will end with a value of zero if no modifier keys were detected.

Reading the modifier keys

We need to search the sModifierKeys argument for any modifier keys given by the user. They will be in plain text like “Control” or “Option+Shift” in double quotes.

What about Control?

Let’s consider a single modifier key to get started. How can we detect whether the user specified just a “Control” key?

Use lowercase characters

Text searches in VBA are case sensitive, so it will simplify the logic as we move forward to just make the modifier keys text all lower case using the standard VBA function LCase(…).

sModifierKeys = LCase(sModifierKeys)

LCase(…) does what its abbreviated name implies. It converts all uppercase alphabetic characters in the text to lowercase without affecting existing lowercase text or non-alphabetic characters. We then save the result back in the same sModifierKeys variable using an equals = sign, so we don’t need to create another one just to hold the lower case version of the text.

Using Like to search text (review)

We’ve previously used a search operator called Like to find text in a String variable. Using it to search text looks something like:

SomeText Like SearchPattern

Don’t get hung up on Like being called an “operator”. A plus + sign is also an operator that adds numbers and spits out another number as the result. Like is also an operator in the sense that it applies a search pattern on the right to some text on the left and gives us a True or False (Boolean) result based on whether it found a match.

In the statement above, SomeText is the plain text variable to search usually given as a string variable storing the desired text. The search pattern on the right specifies a pattern of characters to match in SomeText, but it also allows for a few special wildcard characters to allow more general searches. For example, an asterisk * tells the Like search to allow any (as many as needed) or no characters in that position. Other wildcard characters exist, but we don’t need them for this function.

Looking for Control

We already converted the entire text of the sModifierKeys variable to lowercase, so we want to know whether the word “control” is anywhere inside it.

Our search pattern will be something like “control”, but this isn’t sufficient for our search since nothing else is present in the pattern before or after the word. More specifically, this pattern would require the searched text to begin but also immediately end with the word “control”, so this pattern looks only for the explicit text “control” not allowing any other characters in the string. That’s not very useful for our problem.

We need to allow for other possible characters on either side. The revised pattern looks like “*control*”. where the asterisks tell Like to allow no or any number of characters before or after the word control. Our revised search operation now looks like:

sModifierKeys Like "*control*"

This results in a True or False value depending on whether the search matched the text control anywhere in the sModifierKeys variable.

Tentative conditional statement

Since Like returns a True or False value, we can set up a conditional If statement using it to do something when we detect the word control in our text variable.

If sModifierKeys Like "*control*" Then
' Do something when control is matched as a modifier key ...
End If
Conditional statement for Control key

Going back to searching the sModifierKeys text, if the word “control” is matched anywhere in the text, then we add its WdKey constant wdKeyControl to myKeyCode.

If sModifierKeys Like "*control*" Then
myKeyCode = myKeyCode + wdKeyControl
End If

The wdKeyControl constant is automatically converted by VBA to a long number type for the addition. The result is stored back in the same myKeyCode variable, so we can also add any other modifier keys that may be present.

When it’s clear, I prefer the more compact statement on one line.

If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl

What about the other modifier keys?

We have two more modifier keys to consider (three in Word for Mac) which can occur in any combination or order.

Ughhh.

The respective modifier key constants in the WdKey table are:

  • Command → wdKeyCommand
  • Control → wdKeyControl
  • Alt → wdKeyAlt
  • Option → wdKeyOption
  • Shift → wdKeyShift

Despite the WdKey table documentation, the constant values are not the same between Word for Windows or Mac, but the overlapping names work and translate correctly between the two systems. As long as we’re referring to the WdKey constants, it doesn’t matter.

Conditional statements for other modifier keys

We just repeat the above conditional statement for each relevant modifier key.

If sModifierKeys Like "*alt*" Then myKeyCode = myKeyCode + wdKeyAlt
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

Or the equivalent for Mac modifier keys.

If sModifierKeys Like "*command*" Then myKeyCode = myKeyCode + wdKeyCommand
If sModifierKeys Like "*option*" Then myKeyCode = myKeyCode + wdKeyOption
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

In these steps, the order doesn’t matter since we’re just adding the constants together.

Revised modifier keys build key code

These steps are much more concise than the previous GetKeyCode function with the added benefit of being able to automatically assign all four modifier keys in Word for Mac.

Windows

Putting the keycode steps together, in Word for Windows, we have:

' Independently check each modifier key given in sModifierKeys and add
' the keycodes together
Dim myKeyCode As Long
myKeyCode = 0 ' Initialize key code value to no modifier keys
If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl
If sModifierKeys Like "*alt*" Then myKeyCode = myKeyCode + wdKeyAlt
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

Mac

In Word for Mac, we add the Command key search and swap out Alt for Option:

' Independently check each modifier key given in sModifierKeys and add
' the keycodes together
Dim myKeyCode As Long
myKeyCode = 0 ' Initialize key code value to no modifier keys
If sModifierKeys Like "*command*" Then myKeyCode = myKeyCode + wdKeyCommand
If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl
If sModifierKeys Like "*option*" Then myKeyCode = myKeyCode + wdKeyOption
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

Return value

The function should return the keycode number for any valid combination of modifier keys and a MainKey. Our modifier key value is stored as myKeyCode, and the MainKey was given by the user. To get the final keycode, we just add the two values together and assign the number to the function name for the return value.

GetKeyCode = myKeyCode + MainKey

Error checks

Catching errors as early as possible will inevitably save time and frustration later. For example, if someone tries to assign a macro to “Shift+A”, they won’t be able to type a capital A … whooops. Even worse, he or she may unintentionally trigger the macro since most typists will automatically tap Shift+A without even thinking about it. This key combination is almost certainly an error.

Or what if someone tried to override the Return key? This might work if you want to test the current paragraph and perhaps adjust the next paragraph style before inserting the paragraph mark as normal, but in most cases, something probably went wrong.

Not using On Error to catch runtime errors

These error checks are different than using On Error to detect a VBA “runtime error” (literally an error that occurs while a macro or function is running).

On Error GoTo SomeErrorLine ' Not needed in this function

Any logical errors may cause a VBA runtime error, but this is more likely to occur when doing the keybinding assignment rather than when calculating a keycode for the key combination. VBA most likely would not detect an error when carrying out any steps in this function.

What to do when a logical error occurs?

What do we do when we encounter a logical error based on the keycode value?

  • Set an invalid return value to notify the user that a problem occurred
  • Pop up an error message (optional)
  • Exit the function

Return invalid result

Since this is a function, it needs to return a value indicating the final result. Otherwise, the default will be set to whatever VBA decides (usually 0 for numbers). Keycodes are always positive numbers, so a negative value clearly indicates the function did not find a valid keycode.

GetKeyCode = -1 ' Invalid result

Optional error message with MsgBox

Too many pop ups are annoying, so including one in the error steps is a preference. You might defer this message to the macro that attempts to assign the keyboard shortcut.

MsgBox "Notify user with an error message ..." ' Optional?

Exit the function

We then exit the function, so we don’t run any additional steps that may change our invalid return value. Plus we’re done, so why bother doing anything else?

Exit Function

Validate MainKey argument

We used a Long number type for our MainKey function parameter since VBA does not validate any values we assign to an enumeration variable anyhow, but even if it did, we want to allow keyboard shortcuts for the arrow keys, but they aren’t in the WdKey enumeration.

Huh?

One of the purported advantages of enumerations is we can declare variables or constants using it as a new data type. Suppose we had a variable declared to be a WdKey enumeration type.

Dim SomeVariable As WdKey ' Declare a WdKey enumeration variable

Unfortunately, we can assign any value to the variable, so it doesn’t really do much good for us in VBA.

' WdKey variable can still be assigned any Long number value
SomeVariable = 9999 ' This works (bad VBA!)

Yep, don’t you feel the outrage.

This defeats half the purpose of an enumeration variable in my opinion. We would still need to manually check whether we can even assign a keyboard shortcut to the given key, so we might as well just use a Long number type.

The main key validation steps are simple but a little messy, so we previously implemented a quick function to check the key constant for us.

Main key validation function

Our key validation function IsValidKeyBindingMainKey was covered in a separate article. It takes one parameter, the Key value to test. For this article’s GetKeyCode function, that is the MainKey argument.

IsValidKeyBindingMainKey(MainKey) ' Is MainKey valid?

It returns a True or False (Boolean) value whether the key can or cannot be used as a main key for any keyboard shortcut, but it’s actually easier in the error check if we detect the reverse (is the key invalid?). We just apply the Not operator to reverse the True or False result.

Not IsValidKeyBindingMainKey(MainKey) ' Is MainKey invalid?

Since the function returns a True-False value, we can use it directly in a conditional statement.

Main key validation conditional statement

If the key is invalid, immediately exit the function with an invalid return value (see above).

' Verify MainKey can be used in any keybinding
If Not IsValidKeyBindingMainKey(MainKey) Then
' Invalid main key value for keybinding
MsgBox "Include an error message ..." ' Optional

GetKeyCode = -1 ' Invalid value
Exit Function
End If

This check should be placed at the beginning of the function. The error message is a placeholder since they can look messy, and you may prefer to defer it to the main keyboard shortcut assignment macro anyhow.

Why a negated error check?

Let’s take a brief detour to talk about programming style and error checks.

Is it awkward to structure the error check as a negative condition to trigger exiting the function immediately?

It's not the only way to set up the conditional statement, but I don’t like Else parts to If statements if they’re not necessary or if they make the function look cluttered.

What we’re doing with this initial error check has a bird’s eye view like:

If bad result (stated as True) Then
' Exit function now ...
End If

' If we get here, the result above was good.
' Continue with the rest of the function ...

The following is a more classical version of a conditional statement.

If good result Then
' Do good steps (which could stretch out) ...
Else
' Bad result, so exit function ...
End If

This might work if the “good steps” are more limited. The problem is this function stretches out enough that jamming them into the True part of an If statement clutters the function and makes it harder to read even with proper indentation.

Error check for no modifier key

We can assign macros or Word commands to shortcut keys even without using any modifier keys, but be careful.

Overriding fundamental keys could cause problems

Most regular keys perform actions fundamental to word processing, so they should be overridden with caution since replacing them could hinder regular typing or editing. For example, we can override the Return key, but we better have a good reason. Even then, it should probably be done in the context of somehow improving what the key already does while maintaining the default behavior.

What keys are okay without modifier keys?

Some suggested key bindings that may work well without modifier keys such as the function keys, Page Up or Down, arrow keys, Insert, Delete, Home, End, and maybe a few others. This list isn’t exhaustive, and most variations have not been tested.

While the function keys are also assigned to specific commands in Word, they don’t perform actions as fundamental as many other keys, so they are probably the most used keys in this regard.

Shift key is also a problem

Using the Shift key as the only modifier key can also be a problem because its action is pre-defined for so many keys, so we’ll also use this error check to validate those key combinations. Further, trying to use Shift for key bindings with or without various numpad keys may not work as expected based on how the keyboard works or how the operating system interprets numpad input.

Some combinations are not recognized

Some special keys and even some punctuation keys may not even be recognized by Word’s key binding add method. To my knowledge, no specific documentation exists on unrecognized key combinations, and any restricted keys depends on whether we’re using Word for Windows or Mac. Except for the Delete key, we’ll ignore this aspect for any error checking.

Test any unusual keyboard shortcuts before setting them up to be automatically assigned. A previous article (for Mac but in a different context) or video (for Windows) cover manually assigning keyboard shortcuts, and the same dialog works to test various key combinations without assigning anything.

Checking for no modifier keys

Since the logic is a little tricky, let’s first consider no modifier keys and leave including the Shift key for later. We earlier assigned an initial value of zero to myKeyCode before searching for any modifier keys, so myKeyCode will still have a zero value if no modifier keys were found.

myKeyCode = 0 ' Detects no modifier keys (also initial value)

Unfortunately, conditions in VBA resemble assignments with a single equals = sign, but VBA determines the result by context. Putting this into a conditional statement where it results in a True or False (Boolean) value, we would have:

If myKeyCode = 0 Then
' No modifier keys found, so do these steps ...
End If

The catch is we want to allow no modifier keys for a keyboard shortcut with certain special keys.

Ughhh.

Key validation function

Unfortunately, checking for valid keys when no modifier keys are used is a little messy to include everything here. The respective values are scattered among the WdKey constants table, and the arrow keys are defined in a different VBA keycode constants table, so we end up testing against multiple constant ranges and some specific constants. The various tests stretch out a bit, so it’s useful to have a small utility function that detects any invalid main keys under these circumstances.

Validating main key possibilities

A previous function in a separate article validated whether a given key can be used as a main key without being combined with a modifier key. Using the function looks like:

IsValidIsolatedMainKey(SomeKey)

The name is long to be descriptive. SomeKey is a key constant value to test. The function returns a True or False (Boolean) value based on whether a key match was found. Can we use the key for an “isolated” main key shortcut or not?

Our error check needs to know if the MainKey is invalid when used by itself, so we include a Not operator to switch valid to invalid or the reverse.

' Is main key invalid when used by itself?
Not IsValidIsolatedMainKey(MainKey)
Simpler case with no modifier keys (ignore Shift key for now)

Our conditional statement sketch (called “pseudocode”) to catch an invalid isolated keyboard shortcut assignment might be:

If no modifier keys are given with a main key that requires them Then
' No modifier keys were given for a main key that requires modifier keys,
' so do these steps ...
End If

Our tested key is MainKey from the function argument. We need to combine the no modifier keys condition with the invalid main key check, so the compound condition looks like:

' Detect no modifier keys And MainKey is invalid without them
myKeyCode = 0 And Not IsValidIsolatedMainKey(MainKey)

The And operator requires both conditions to be True before the compound condition is True, so we only reject the keyboard shortcut if no modifier keys were given, and the MainKey is invalid without them.

The conditional statement is a little confusing because we reject the keyboard shortcut on a True result, so True corresponds to an error where we exit the function. Setting up the correct compound condition is kind of like understanding a double negative in a sentence. Your head tilts to the side, and you squint as you work through the logic.

A tentative conditional statement would look like:

If myKeyCode = 0 And Not IsValidIsolatedMainKey(MainKey) Then
' No modifier keys were given for a main key that requires modifier keys,
' so do these steps ...
End If

The trick is allowing the Shift key without disturbing this condition.

Checking for only a Shift modifier key

Technically, creating a keyboard shortcut with Shift plus some other main key is allowed, but it’s questionable for most keys. Fortunately, using Shift as a modifier key is limited to almost the same keys as if we specified no modifier keys at all (probably not exactly but we’ll treat them the same for simplicity).

At this point, we’ll assume we’ve searched the keyboard shortcut text in sModifierKeys for each modifier key and added the respective constants for those found to the myKeyCode variable. The Shift key has a constant value of wdKeyShift in the WdKey enumeration, and myKeyCode had an initial value of zero. If only a Shift key was specified, myKeyCode will equal wdKeyShift since it would be the only constant added.

myKeyCode = wdKeyShift ' Detect only Shift modifier key

We combine this Shift key condition with the previous one for detecting no modifier keys. Since either condition is a problem, we use an Or operator.

' Compound condition to detect no modifier keys or just Shift
myKeyCode = 0 Or myKeyCode = wdKeyShift

Compound conditional statement

The following conditional statement is a little more complicated than many we’ve used. We want to abort the keyboard shortcut assignment if the MainKey is invalid for both no modifier keys or just a Shift modifier key.

How do we put this into a conditional statement?

Revised conditional statement

Let’s try to incorporate the Shift key. A tentative compound conditional statement sketch is:

If no modifier keys or only shift key and main key requires them Then
' No modifier keys or just the Shift key was given with an invalid main key,
' so do these steps ...
End If

We need to be careful when converting an English statement into a logical compound condition because And is evaluated before Or in a compound condition. In programming speak, the And operator has a higher logical precedence than Or much like multiplication has a higher precedence than addition in general mathematics.

Ensure modifier keys are tested together

To ensure both conditions for any modifier keys are tested first, we wrap them in parentheses.

(myKeyCode = 0 Or myKeyCode = wdKeyShift) ' ... and is MainKey invalid by itself?
Evaluate all conditions together

If either condition is True, then we then care about whether the MainKey is invalid. On the other hand, if the main key is invalid, then it doesn’t matter what modifier keys were given. Thus, we use an And operator which requires both parts to be True.

(myKeyCode = 0 Or myKeyCode = wdKeyShift) And Not IsValidIsolatedMainKey(MainKey)

The resulting conditional statement is:

If (myKeyCode = 0 Or myKeyCode = wdKeyShift) And _
Not IsValidIsolatedMainKey(MainKey) Then
' No modifier keys or just the Shift key was given with an invalid main key
MsgBox "Include an error message ..." ' Optional

GetKeyCode = -1
Exit Function
End If

This error check should be placed at the end of the function just before the valid returned keycode value.

Gotchas

What could go wrong? Surely nothing would—

Watch out for modifier key constant values between Windows and Mac

Despite the values listed Microsoft WdKey enumeration documentation, the modifier key constants are not the same in Word for Mac. Kind of makes sense since Mac allows up to all four modifier keys, but it would be nice if it were documented.

Little things like this show the dangers of tinkering with the inner workings of functions that are not our own. We need to know what we can and cannot do. Fortunately, we’re mostly safe here if we use the constant names not specific numbers read off the table.

Lowercase alphabetic characters

What happens if we try to send the function a lowercase letter value?

This would probably happen if a user were trying to give the function the ascii value of something like the character "a" (which is 97). Unfortunately, we cannot error check these specific numerical values because the ascii range of lowercase letters overlaps with the numeric keypad and function key constants in the WdKey enumeration table. They are valid values, so we cannot exclude them.

Final improved get keycode function

This version is a tad messier than the previous, but it’s more direct and dare I say cleaner. The error checks make it a little safer to use. An underscore character _  just continues the current line through the next line as far as the VBA editor is concerned. Using it just allows a human to read the long line more easily.

If you prefer a combined version for Windows or Mac, see the extended version below which also includes an override parameter.

Windows

Putting all the steps together, we have:

Function GetKeyCode(ByVal sModifierKeys As String, MainKey As Long) As Long
' Return the unique keycode for assigning a keyboard shortcut keybinding
' Modifier keys string should spell out each word like
' "Control", "Alt", "Shift" but is not case sensitive
' MainKey should be a key constant from WdKey enumeration or VBA keycode
' constants

' Verify MainKey can be used in any keybinding
If Not IsValidKeyBindingMainKey(MainKey) Then
' Invalid main key value for keybinding
MsgBox "GetKeyCode error: Invalid main key value for keybinding " + _
sModifierKeys + vbCr + " with main key = " + CStr(MainKey)

GetKeyCode = -1 ' Invalid value
Exit Function
End If

' Convert to lower case (does not modify outside value)
sModifierKeys = LCase(sModifierKeys)

' Independently check each modifier key given in sModifierKeys and add
' the keycodes together
Dim myKeyCode As Long
myKeyCode = 0 ' Initialize key code value to no modifier keys
If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl
If sModifierKeys Like "*alt*" Then myKeyCode = myKeyCode + wdKeyAlt
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

' Perform error checks for no or only a Shift modifier key
If (myKeyCode = 0 Or myKeyCode = wdKeyShift) And _
Not IsValidIsolatedMainKey(MainKey) Then
' Invalid modifier keys given for MainKey
MsgBox "GetKeyCode error: Invalid main key value for keybinding " + _
sModifierKeys + vbCr + _
"For modifier keycode = " + CStr(myKeyCode) + _
" with main key = " + CStr(MainKey)

GetKeyCode = -1 ' Invalid value
Exit Function
End If

' Add MainKey to keycode and return value
GetKeyCode = myKeyCode + MainKey
End Function

There are a few places we could tweak the function to make it better, but this is straight forward enough. I regret how much longer the error messages make the function seem, but they’re essential to any well written function that will not turn and bite you later, or at least it will warn you before it bites you.

Mac

The Mac version is nearly the same, but the modifier keys are different enough that it seemed clearer to define a separate function. Most people only need one version.

Function GetKeyCodeMac(ByVal sModifierKeys As String, MainKey As Long) As Long
' Return the unique keycode for assigning a keyboard shortcut keybinding
' Modifier keys string should spell out each word like
' "Command", "Control", "Option", "Shift" but is not case sensitive
' MainKey should be a key constant from WdKey enumeration or VBA keycode
' constants

' Verify MainKey can be used in any keybinding
If Not IsValidKeyBindingMainKey(MainKey) Then
' Invalid main key value for keybinding
MsgBox "GetKeyCode error: Invalid main key value for keybinding " + _
sModifierKeys + vbCr + " with main key = " + CStr(MainKey)

GetKeyCodeMac = -1 ' Invalid value
Exit Function
End If

' Convert to lower case (does not modify outside value)
sModifierKeys = LCase(sModifierKeys)

' Independently check each modifier key given in sModifierKeys and add
' the keycodes together
Dim myKeyCode As Long
myKeyCode = 0 ' Initialize key code value to no modifier keys
If sModifierKeys Like "*command*" Then myKeyCode = myKeyCode + wdKeyCommand
If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl
If sModifierKeys Like "*option*" Then myKeyCode = myKeyCode + wdKeyOption
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

' Perform error checks for no or only a Shift modifier key
If (myKeyCode = 0 Or myKeyCode = wdKeyShift) And _
Not IsValidIsolatedMainKey(MainKey) Then
' Invalid modifier keys given for MainKey
MsgBox "GetKeyCode error: Invalid main key value for keybinding " + _
sModifierKeys + vbCr + _
"For modifier keycode = " + CStr(myKeyCode) + _
" with main key = " + CStr(MainKey)

GetKeyCodeMac = -1 ' Invalid value
Exit Function
End If

' Add MainKey to keycode and return value
GetKeyCodeMac = myKeyCode + MainKey
End Function

Combined OS version

If you’re happy with one of the Windows or Mac specific versions above, skip this, but we make several more improvements.

  • Since the differences between the Word for Windows or Mac versions only exist in a few lines, combine the steps into one function that works for either Mac or Windows
  • Try to tidy up the messy error messages a little
  • Add an override parameter we can use to force a keyboard shortcut assignment even if the validation function says the main key is invalid

Common steps

Between Word for Windows and Mac, both systems have Control and Shift keys. The keycode is just a number, so order doesn’t matter for the addition of the modifier key constants as they are identified. We can move the searches for the two common keywords together.

' Common steps between Word for Windows or Mac
' Independently check each modifier key given in sModifierKeys and add
' the keycodes together
Dim myKeyCode As Long
myKeyCode = 0 ' Initialize key code value to no modifier keys
If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

Steps different between Windows or Mac

On a Mac, we need to also search for the Option and Command key texts. On Windows, it’s only Alt unless Windows some day wonderfully enables the Windows Command key for Word keyboard shortcuts!

Preprocessor quick review

We introduced the preprocessor directives in another article, but here is a whirlwind tour. They are basically special statements preceded by a hashtag “#” that are “preprocessed” before VBA runs the macro. For this function, we need a conditional statement #If … #Else … #End If to run different steps between the two operating systems. Otherwise, they act like regular VBA If-Else statements.

Mac is a True or False (Boolean) option that lets VBA know whether we’re in Word for Mac. If not, then we’re in Windows.

' Preprocessor directive to detect Mac or Windows
#If Mac Then
' Do these steps if running the function on a Mac
#Else
' Do these steps if running the function in Windows
#End If

Searching for Mac or Windows specific modifier keys

Putting in the key specific searches for each OS, we have:

' Add modifier keys that differ between Mac and Windows
#If Mac Then
' Do these steps if running the function on a Mac
If sModifierKeys Like "*command*" Then myKeyCode = myKeyCode + wdKeyCommand
If sModifierKeys Like "*option*" Then myKeyCode = myKeyCode + wdKeyOption
#Else
' Do these steps if running the function in Windows
If sModifierKeys Like "*alt*" Then myKeyCode = myKeyCode + wdKeyAlt
#End If

We basically just include this after the common modifier keys are searched.

It does make the function longer and a little messier, but if you happen to use both Windows and Mac like I do, we can get by with a single function rather than two.

More organized error messages

Let’s take a short detour to talk about custom error messages. We’re not considering error messages from VBA when something goes wrong. We’re talking about our own error messages designed to warn us of impending doom … well, maybe just some trouble ahead.

My error messages tend to stretch out because I like to include extra information, so the message is more useful when something goes wrong … and it will eventually.

MsgBox review

MsgBox is the standard VBA function to pop up a message dialog.

MsgBox "Include some message ..."

We can also just refer to a plain text (String) variable.

Dim SomeMessage As String ' SomeMessage is plain text
SomeMessage = "This is an error message" ' Assign any message to the text variable
MsgBox SomeMessage

We can even add strings together (called "concatenate") using a plus + sign or an ampersand &.

MsgBox "Something went wrong: " + SomeMessage

We can add some extras to the MsgBox dialog like a title or shiny little warning icon to be more professional and representative of the error message, but I tend to use just a basic dialog without any frills. A tentative error message for this function is:

MsgBox "GetKeyCode error: Invalid main key value for keybinding shortcut " + _
sModifierKeys + vbCr + _
"For modifier keycode = " + CStr(myKeyCode) + _
"with a main key = " + CStr(MainKey)

A little long, huh?

CStr function for numbers

Here we need the standard VBA function CStr(…) to convert the number variables to plain text. We can get away without it if we use an ampersand & rather than a plus + sign to concatenate the text. The ampersand mostly does the same thing, but I just don’t like how it looks. Using a plus sign feels more natural to me.

Simplifying the error message

We won’t reduce the content of the error message because we still need the information, but we can organize it a little better to make the steps easier to read.

First error message

The first part of the message is problem specific.

MsgBox "GetKeyCode error: Invalid main key value"
' followed by more error information ...

When several error messages communicate similar information, it’s annoying to repeat everything. When the rest of the message is repeated elsewhere in the function, we save the common text to a plain text variable. We’ll call it sErrorInfo here.

' Save common part of error message information
Dim sErrorInfo As String, sKeyCode As String
sErrorInfo = "for keybinding shortcut " + sModifierKeys + vbCr + _
"with a main key = " + CStr(MainKey) + vbCr

The sKeyCode variable will be used below, but we declare it here to save a line later. For the sErrorInfo variable, we just add the common error text together. Most of it can be defined once at the top of the macro. Then our error message when needed is just:

MsgBox "GetKeyCode error: Invalid main key value " + sErrorInfo
Second error message

Unfortunately, the error messages in this function are just different enough to be annoying. We get additional keycode information by the end, so we store that in another plain text variable.

sKeyCode = " for modifier keys keycode = " + CStr(myKeyCode)

Then we add the new information to our second error message.

MsgBox "GetKeyCode error: Invalid main key value " + sErrorInfo + sKeyCode

We had to set it up at the beginning of the function, but it’s much easier to read. Of course, one could argue it’s just messier in a different way, so it’s definitely a preference.

Optional override parameter

I may occasionally want to override a standard key combination to test something, so I don’t want my own function hindering the test. We could just add the key as a “valid” option in our previous validation functions, but sometimes I just want a singular exception. Toward this end, we’ll add an optional True or False (Boolean) override parameter to the function that will circumvent the latter main key validation.

Function GetKeyCode(ByVal sModifierKeys As String, MainKey As Long, _
Optional BForce As Boolean = False) As Long
' Function steps ...
End Function

The MainKey should still be any valid key, but other than that, setting BForce to True overrides the validation check for an isolated shortcut key or one used with only the Shift key forcing the keycode to be calculated as is.

Optional parameter review

An optional parameter in VBA is preceded by the keyword … Optional.

Optional SomeVariable ' Followed by a type ...

We can use Optional with any “intrinsic” VBA data type such as Long, Integer, String, Boolean, and a few more obscure types. We’ll use Boolean (True or False) here because we just want a Yes or No for whether to override the regular main key validation. We’ll call the parameter BForce.

Optional BForce As Boolean ' Not quite done ...

All optional parameters must have a default value that is assigned to the variable when it is omitted from the function call. Literally add … = SomeValue after the data type where SomeValue should correspond to the correct data type. That is, for a Long value it should be a number; for a Boolean, it should be True or False; etc.

Optional BForce As Boolean = False

For this function, BForce defaults to False meaning do not force a keycode calculation for an invalid main key.

All optional parameters must lie at the end of the parameter list for the function. If they are omitted when calling a function, they must be omitted in order from last to first optional parameter. A few exceptions exist with VBA native functions, but we cannot do this with our own parameters.

Including the force parameter

We previously validated the main key based on whether it worked the given modifier keys configuration. The validation conditional statement looked like:

' Previous error checks for no or only a Shift modifier key
If (myKeyCode = 0 Or myKeyCode = wdKeyShift) And _
Not IsValidIsolatedSpecialKey(MainKey) Then
' Error steps where we exit the function ...
End If

Now we want to incorporate the force optional parameter. Since the above compound condition is stated in the negative (meaning exit the function if the conditional statement is True), we also negate the force parameter:

Not BForce

In addition to the other parameters, Not BForce must be True before the error steps are run. This requires another And operator in the since all three conditions must now be True before the error steps are run.

If Not BForce And (myKeyCode = 0 Or myKeyCode = wdKeyShift) And _
Not IsValidIsolatedSpecialKey(MainKey) Then
' Error check steps where we exit the function ...
End If

If BForce is True then Not BForce is False, and the error steps are skipped regardless of the other conditions which is what we need for an override parameter.

Using the force parameter

When using the force parameter in another macro (see upcoming article), we just add the extra argument to the function call when needed. Let’s pretend our working keycode variable with a Long number type is called aKeyCode

Dim aKeyCode As Long ' Temporary keycode variable

If we tried to create a keyboard shortcut for Shift+Return, we need to specify "Shift" as the modifier key text in the first argument. The Return key has a constant wdKeyReturn from the WdKey table which we use as the second argument. A “regular” GetKeyCode function call attempt might look like:

aKeyCode = GetKeyCode("Shift", wdKeyReturn)

In this function call, the BForce parameter has been omitted, so it has a default value of False inside the function based on the Optional parameter. As written, the above error check along with the key validation function in the previous article would reject overriding Shift+Return as a valid keyboard shortcut combination.

If we want to override it and ensure the keycode is calculated and stored in the aKeyCode variable without triggering an error, we add the force argument as True in the last argument inside the parentheses.

aKeyCode = GetKeyCode("Shift", wdKeyReturn, True)

Then we could continue with the rest of the keyboard shortcut assignment function as needed.

Revised function with forced override

Incorporating all the improvements, the result is:

Function GetKeyCode(ByVal sModifierKeys As String, MainKey As Long, _
Optional BForce As Boolean = False) As Long
' Return the unique keycode for assigning a keyboard shortcut keybinding
' Modifier keys string should spell out each word (not case sensitive)
' "Control", "Alt", "Shift" on Windows
' "Command", "Control", "Option", "Shift" on Mac
' MainKey should be a key constant from the WdKey enumeration or a VBA keycode
' constant

' Save common part of error message information
Dim sErrorInfo As String, sKeyCode As String
sErrorInfo = "for keybinding shortcut " + sModifierKeys + vbCr + _
"with a main key = " + CStr(MainKey) + vbCr

' Verify MainKey can be used in any keybinding
If Not IsValidKeyBindingMainKey(MainKey) Then
' Invalid main key value for keybinding
MsgBox "GetKeyCode error: Invalid main key value " + sErrorInfo

GetKeyCode = -1 ' Invalid value
Exit Function
End If

' Convert to lower case (does not modify outside value)
sModifierKeys = LCase(sModifierKeys)

' Independently check each modifier key given in sModifierKeys and add
' the keycodes together
Dim myKeyCode As Long
myKeyCode = 0 ' Initialize key code value to no modifier keys
If sModifierKeys Like "*control*" Then myKeyCode = myKeyCode + wdKeyControl
If sModifierKeys Like "*shift*" Then myKeyCode = myKeyCode + wdKeyShift

' Add modifier keys that differ between Mac and Windows
#If Mac Then
' Do these steps if running the function on a Mac
If sModifierKeys Like "*command*" Then myKeyCode = myKeyCode + wdKeyCommand
If sModifierKeys Like "*option*" Then myKeyCode = myKeyCode + wdKeyOption
#Else
' Do these steps if running the function in Windows
If sModifierKeys Like "*alt*" Then myKeyCode = myKeyCode + wdKeyAlt
#End If

' Perform error checks for no or only a Shift modifier key
If Not BForce And (myKeyCode = 0 Or myKeyCode = wdKeyShift) And _
Not IsValidIsolatedSpecialKey(MainKey) Then
' Invalid modifier keys given for MainKey
sKeyCode = " for modifier keys keycode = " + CStr(myKeyCode)
MsgBox "GetKeyCode error: Invalid main key value " + sErrorInfo + sKeyCode

GetKeyCode = -1 ' Invalid value
Exit Function
End If

' Add MainKey to keycode and return value
GetKeyCode = myKeyCode + MainKey
End Function

This version uses a preprocessor directive to separate the different steps between Windows and Mac. I would use this version since I have many macros, but it’s a personal preference whether the extra mess is worth having only one GetKeyCode function.

Affiliate Links

If you're interested in using Word or another tool related to the article, check out these affiliate links. I may make a small commission if you purchase when using them, but there is no increase in cost for you, and it helps to support this site and associated content.

I've been using Microsoft for Business for commercial use (that's us writers) on one of the lower pricing tiers for years. I get to use my macros, have online storage, and don't have to worry about software updates.