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

Key check for main key bindings

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

A previous function generated a keycode to “bind” a macro or Word command to a keyboard shortcut. When including a few error checks, we need to validate whether a given key should be allowed for a shortcut when used without any modifier keys like Control or Option (Alt on Windows). The function doubles for validating keyboard shortcuts accessible when only a Shift modifier key is used since Shift has a special meaning for most keys on a keyboard. We also add a function to validate any main key assignment.

Thanks for your interest

This content is part of a paid plan.

Key check for isolated main key bindings

When automatically adding or updating keyboard shortcuts, we probably would not want someone to override the Return key by itself without a good reason. Similarly, the Shift key has a well-established action with most keys on a keyboard, so we also probably wouldn’t want someone to override something like Shift+A. Unfortunately, both are possible, and accidents happen especially if you have dozens of macros.

As one of several keybinding error checks implemented in a separate article, we’re testing whether a given key can be used as a shortcut assignment without a modifier key or when combined just with the Shift key. Unfortunately, the relevant special key values are scattered throughout the WdKey enumeration, and we even need to access another keycode constants table to cover the arrow keys. While the steps are straight forward, they stretch out enough to make the sequence of checks messy, so it is convenient to implement a separate function to tell us yay or nay to the question.

This article is but part of a series of mostly member articles:

The functions below perform niche but necessary tasks for validating potential keybinding assignments. We'll start with isolated main key bindings or those with only a Shift modifier key and then trivially extend it to any keyboard shortcut combination in the extended content below since both cases are useful in the final assignment macros.

Isolated keybinding keys

We first consider whether keyboard shortcuts automatically assigned to a single key such as F3 are valid for a keybinding assignment. If not, we will return an invalid key result, so the user can skip the shortcut assignment.

We can assign key bindings without modifier keys to many keys, but caution should be exercised since most would be problematic to override. Some useful special keys to consider include Page Up/Down, arrow keys, Insert, Enter (Return), Delete/Backspace, Home, End, and maybe a few others. Of course, adjust your own list as desired.

Since the Shift key has a well-established action with most keys on a keyboard, we can also apply the function to validate any keyboard potential shortcuts that use Shift as a lone modifier key. For example, we might wish to create a macro that overrides Shift+Return. The keybinding restrictions aren’t exactly the same between the two cases, but this function provides an example to adapt for any special cases you may need.

We will refer constant names rather than specific numbers since the constant names are clearer, and they help safeguard our function against some changes to the constant tables.

Afterward, we will trivially modify the resulting function in the extended content below to validate any main key.

Function skeleton

The function skeleton is:

Function IsValidIsolatedMainKey(Key As Long) As Boolean
' Return a Boolean value based on whether the indicated Key constant can be used
' without any modifier keys when keybinding with BuildKeyCode

End Sub

The function is similar enough between Word for Windows or Mac that we only need minimal changes for either operating system.

Reasons for creating functions

Some functions are created because they are useful across various macros where they simplify creating more complex macros. Others like this one just take a group of messy steps and encapsulate them to make another macro or function easier to read, but they may or may not find use elsewhere. Although, we occasionally create a tool only to later realize it is indeed useful elsewhere.

Parameters

The only parameter required is a key constant value identifying which key is being checked. We’ll just call the variable Key with a capital “K” to indicate the value does not change within the function. I would prefer to require the key constant to be a WdKey type from the WdKey enumeration table since restricting possible values to those in the table allows fewer chances for any problems later, but we also need to check the arrow keys which do not appear in that table.

Ughhh.

Thus, we’re stuck with using a Long number type (basically a big Integer in VBA) for the key value type. This is not a problem in this function since VBA automatically converts enumeration constants to Long values without any issues when testing against the respective key constants.

Remember an enumeration is basically a list of easy-to-read, pre-defined constant values, but they do possess more utility than just better names.

Return value

The function returns True (As Boolean) if the corresponding key can be used without other modifier keys and False if not.

IsValidIsolatedMainKey = False ' Example function return result assignment

Useful main key constants

Useful key constant ranges to detect valid isolated main keys are mostly found in the WdKey enumeration:

Function keys → wdKeyF1 To wdKeyF16
Page Up and Page Down → wdKeyPageUp To wdKeyPageDown
End and Home → wdKeyEnd To wdKeyHome

Page Up and Down as well as End and Home form a contiguous sequence, so we can simplify it to:

Page Up through Home → wdKeyPageUp To wdKeyHome

The arrows keys are from the VBA keycodes table since they aren’t in the WdKey enumeration.

Arrow keys → vbKeyLeft To vbKeyDown ' From VBA keycodes

Other specific keys include:

Insert → wdKeyInsert
Backspace → wdKeyBackspace ' Backward Delete even on a Mac

Unfortunately, Delete only works in Word for Windows for custom key bindings, so we need to do some extra work to properly handle it.

Delete → wdKeyDelete ' Forward Delete on Windows

Delete and Backspace keys

Windows systems use both Backspace and Delete keys, and the respective constants in the WdKey table are wdKeyBackspace and wdKeyDelete.

Delete on a Mac

The Delete key on a Mac acts like Backspace in Windows, and Fn+Delete acts like forward Delete. While the forward Delete key exists and works for standard actions at the system or Word level, we cannot bind a keyboard shortcut in Word to any key combination using either the Fn (Globe) or the Delete key. That’s a shame because it is convenient in Word for Windows to mimic Control+Backspace (delete previous word) and Control+Delete (delete next word) to work similarly for sentences or heading content.

For example, I assign Alt+Backspace and Alt+Delete to two macros that quickly delete to the beginning or end of a sentence. The similar key patterns make them easy to remember, and I use them often when writing. I can’t emulate the forward Delete key combination with something like Fn+Option+Delete on a Mac, so I need to use a key combination like Control+Option+Delete on a Mac. It’s not the end of the world, but the asymmetry in the keyboard shortcuts compared to the actions is disappointing and a little awkward.

The name confusion between the deletion keys is unfortunate, but it is a historical fluke where Macs only included a single “delete” key on their laptops keyboards which deleted backward in the document. Apple called the key “Delete” (I wish they had called it something like “Back” instead) whereas Windows systems included both keys and called them Backspace and Delete, respectively. The Backspace key action is clear by the name, so Delete deleted forward in the document on Windows systems.

Catching the Delete key on a Mac

On a Mac, the Delete key corresponds to wdKeyBackspace in the WdKey enumeration since that is the corresponding action. While the forward Delete key works for standard tasks in applications that support it, the constant wdKeyDelete does not exist in Word for Mac, and it will cause an error if it is used in Word for Mac. As a result, we cannot bind any keyboard shortcuts to it in Word for Mac. Word won’t even recognize forward Delete key as a valid main key shortcut in the customize keyboard options on Mac systems.

Arghhh.

I use both Backspace and Delete (forward Delete on a Mac) in Word for Windows keyboard shortcuts, so I need this function to differentiate between the two keys and let me know the forward Delete key is invalid for any keybinding assignments. It’s inconvenient to create two whole functions to accommodate such a small difference, but fortunately, we’ve covered a solution before in a simple function that gave us the correct double quote characters in Word for Windows or Mac.

Detecting Mac or Windows

If you only use Windows or Mac exclusively, then just include the respective key validation steps, but if you sometimes bounce between them like I do, it’s nice to let the function switch between the different systems automatically.

Use a preprocessor directive

VBA includes a special If-Else statement that is processed before the macro runs in Word. The fancy name is a preprocessor directive, but it just gives us a way to run different steps in Mac or Windows (additional options exist). The directive emulates a regular VBA If-Else statement just with a hashtag in front of the different parts of the command, so it looks like:

' This conditional statement is processed before the macro runs
#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

In this function, we actually only need to do something different in Windows where we add the forward Delete key check. Unfortunately, no "Windows" preprocessor check exists, so we say "Not Mac" instead.

' This conditional statement is processed before the macro runs
#If Not Mac Then
' Do these steps if running the function in Windows
#End If

It adds a little complexity, but if you use both Windows and Mac, it’s nice when we don’t need two versions of the same function where most steps are exactly the same.

Select Case statement review

We have a long list of potential main key constants, so a Select Case statement is the best solution to determine a valid key. Otherwise, we end up with a cluttered sequence of If statements (see previous version for such an example). A Select Case statement is a generalization of an If statement where a Select Case statement literally begins with those words … and ends with End Select.

Select Case SomeVariable
' Include cases ...
End Select

SomeVariable is the variable we’re testing against the various Case values included below. It can be any of the standard data types, and we use a Long number type in this function because we’re testing key values. Each case begins with Case and one or more comparison values.

Select Case SomeVariable
Case Value1:
' Do these steps if SomeVariable has Value1 ...
Case Value2, Value3:
' Do these steps if SomeVariable has Value2 or Value3 ...
Case Value4 To Value5, Value6:
' New (to us) Case value range ...
' Do these steps if SomeVariable is between Value4 and Value5 or is Value6 ...
Case Else
' Do these steps if no other case matches (optional) ...
End Select

We can include as many cases as desired, and each Case can take a comma-separated list of values or value ranges. This flexibility is important since we have many constant values to check in this function.

We finally need Case ranges.

Case ranges

Just for clarity, ranges here are in the context of literal number or String values not document Range objects as used in Word VBA.

Number ranges allow us to condense cases. For example, suppose we checked for counting numbers 10 through 20 using a manual number range.

' A poor Case statement
Case 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20:
' Run steps for case values 10 through 20, inclusive ...

Ughhh.

If this was the only way to check the range, we would probably use an If statement instead. Fortunately, we can literally just use the first and last values separated by “To” like 10 To 20 or SmallNumber To BigNumber in general.

' Range version is shorter and easier
Case 10 To 20:
' Run steps for case values 10 through 20, inclusive ...

If SomeVariable has a value from 10 up to and including 20, it matches the Case range, and the steps below the Case are run.

General Select Case operation

Once the Select Case statement finds a match, the steps just below the Case are run; but once a match is found, no other cases are tested. The Case Else steps are run only if no other matches are found. Including it is optional, in general, but we need it in this function to set a default value for an invalid main key.

Select Case statement

In our Select Case statement, we literally check Key argument "passed" to the function against the constant values in the WdKey table or VBA keycodes constant tables.

Select Case Key
Case KeyConstant1, KeyConstant2 To KeyConstant5, KeyConstant6:
' Found a valid main key constant
Case Else
' No match found so return as invalid key ...
End Select

We return True if the Key argument value given to the function is one of the constants and False if not.

Return result

For this function, we simply assign the return result as the lone Case step. True corresponds to a valid isolated main key.

IsValidIsolatedMainKey = True ' Valid isolated main key result

For the invalid result, simply change the return value to False.

IsValidIsolatedMainKey = False ' Invalid isolated main key result

Key constant ranges

We have a list of constants to check against, but some can be tested as contiguous ranges of constant values reducing the number of individual checks we need to perform. Each range or set of constants is mutually exclusive since key values (mostly) do not overlap within the same table.

Function keys

In the WdKey enumeration, function keys F1 through F16 are defined between wdKeyF1 and wdKeyF16 in a contiguous range with no other key constants in between them.

' Example case range for function keys
Case wdKeyF1 To wdKeyF16:
IsValidIsolatedMainKey = True
Why allow function keys above F12?

Most modern keyboards stop function keys at F12, so why allow any function keys above that?

Actually, this turns out to be nice when assigning nonsense key combinations to trigger a macro from other devices via a keyboard shortcut. For example, if you’re using an Elgato Stream Deck (not an affiliate), you can bind a Word macro to something like Shift+F15 using a button. Another option is macro keys on a fancy mouse or some keyboards. For example, Logitech (not an affiliate) often includes “G-keys” which can be programmed to run various key combinations.

Some higher function number keys may be bound to things like media keys, so not all key combinations work, but it’s a nice trick when it you need it.

Some movement keys

Several other key values happen to lie in a contiguous range: Page Up, Page Down, End, and Home. The sequence starts with wdKeyPageUp and ends with wdKeyHome. Given this range, we check:

' Example case range for Page Up, Page Down, End, and Home keys
Case wdKeyPageUp To wdKeyHome:
IsValidIsolatedMainKey = True
Arrow keys

Unfortunately, the arrow keys do not appear in the standard WdKey table. The reason for the omission is not clear since arrow keys are present on all keyboards. We need a different keycode table to access constants for vbKeyLeft, vbKeyUp, vbKeyRight, and vbKeyDown corresponding to the obvious respective arrows keys.

Fortunately, they form a contiguous sequence starting from vbKeyLeft increasing in value to vbKeyDown. Other than referring to constants from a different VBA table, the form of the conditional statement is the same.

' Example case range for Up, Down, Left, and Right arrow keys
' Arrow keys require different keycode constants table
Case vbKeyLeft To wdKeyDown:
IsValidIsolatedMainKey = True
Backward Delete and Insert

We need to check both Backspace, Delete, and Insert separately since they are not consecutive constants in the WdKey table. We just need to test the specific values, but the Delete constant is a special case, so we'll do that below.

The backward Delete (Backspace in Windows) key constant is wdKeyBackspace which is the same constant value used for the Delete key in Word for Mac. The Insert key constant is wdKeyInsert. Putting them into their own Case statement looks like:

' Example case range for backward Delete and Insert
Case wdKeyBackspace, wdKeyInsert:
IsValidIsolatedMainKey = True
Combined Cases

Since our cases all have the same True result for an isolated main key, we can just combine them into one Case statement separated by commas.

' Combined isolated key constants cases
Case wdKeyF1 To wdKeyF16, wdKeyPageUp To wdKeyHome, vbKeyLeft To vbKeyDown, _
wdKeyBackspace, wdKeyInsert:
IsValidIsolatedMainKey = True

An underscore _ character at the end of the line just continues the line for the VBA editor. It also ignores any blank space between the lines, so we can tab over to make the step easier to read.

Forward Delete on Windows (excluded on Mac)

We mentioned above how to run specific steps in Word for Windows or Mac. In this case, we need to test the forward Delete key constant wdKeyDelete, but it does not exist in Word for Mac. In fact, it will cause an error if you try to use it. Our case statement for wdKeyDelete needs to be wrapped in a preprocessor directive to only run in Windows.

' Forward Delete key is valid in Word for Windows but invalid in Word for Mac
' beyond standard actions since wdKeyDelete is not defined in Word for Mac
#If Not Mac Then
' Case is only included in Word for Windows
Case wdKeyDelete:
IsValidIsolatedMainKey = True
#End If

The result is True in Windows, but the Case is omitted in Word for Mac, so it will default to the Case Else result of False for an invalid key.

Default return value with Case Else

If no keys match, we run the Case Else statement steps. We will set the default return value to False via the function name.

' Example else case for default False result for an invalid key
Case Else:
IsValidIsolatedMainKey = False

Valid isolated main key function

Our function to test whether a given Key is one of the allowed isolated key combinations is:

Function IsValidIsolatedMainKey(Key As Long) As Boolean
' Return a Boolean value based on whether the indicated Key constant can be used
' without any modifier keys when keybinding with BuildKeyCode
' Arrow keys vbKeyLeft To vbKeyDown are from VBA keycode table
' All other keys are from the WdKey enumeration

' Test possible isolated main key values
Select Case Key
' wdKeyBackspace covers Delete key in Word for Mac
Case vbKeyLeft To vbKeyDown, wdKeyF1 To wdKeyF16, wdKeyPageUp To wdKeyHome, _
wdKeyInsert, wdKeyBackspace:
IsValidIsolatedMainKey = True

' Forward Delete key is valid in Word for Windows but invalid in Word for Mac
' beyond standard actions since wdKeyDelete is not defined in Word for Mac
#If Not Mac Then
' Case is only included in Word for Windows
Case wdKeyDelete:
IsValidIsolatedMainKey = True
#End If

Case Else
' Default to an invalid isolated main key
IsValidIsolatedMainKey = False
End Select
End Function

See why we separated this menial task off into another separate function from our GetKeyCode function? With the constants scattered through two different tables, it would unnecessarily clutter the error checking steps. Of course, add any other key checks you want to allow, but use caution for common keys.

If write in use Word for Mac exclusively, just eliminate the unused wdKeyDelete Case along with the preprocessor directive lines preceded by a hashtag “#”.

Validate main key value

Another relevant test when setting up automatic keyboard shortcuts is whether the given main key value is valid regardless of what modifier keys are provided. This is necessary in VBA since a variable defined as an enumeration type (say from the WdKey table) can actually be assigned a value outside of the defined constants.

Yep, you read that right.

Remember an enumeration is just a table of nicely named constant values, so we don’t have to remember specific numbers when writing macros or functions. Usually they’re aggregated in a list of related constants like the WdKey table, but even if I explicitly declare a number variable as a WdKey type:

' WdKey variable can still be assigned any Long number value
Dim SomeVariable As WdKey
SomeVariable = 999 ' This is okay? (Do better VBA)

Ughhh.

It defeats half the point of defining a variable or function parameter as an enumeration type when it doesn’t restrict the possible variable values it can store. Since VBA is a little sloppy with its enumerations, it’s easier to just use a Long number type (a bigger Integer) to store any keycode numbers and validate them as we move forward. Toward this end, we define a quick additional validation function to test whether a given key constant can be used as a main key for any keybinding. We essentially take the function above and add the other WdKey constants that are not modifier keys.

Function skeleton

The function skeleton is:

Function IsValidKeyBindingMainKey(Key As Long) As Boolean
' Return whether the indicated Key constant can be used as a main key when keybinding
' Modifier keys are excluded
' Arrow keys vbKeyLeft To vbKeyDown are from VBA keycode table
' All other keys are from the WdKey enumeration

End Sub

The parameter and return value are the same as the previous function.

It’s a slog to dig through the table and concisely represent all valid main key ranges, but we again include the arrow key constants from the VBA keycodes table and any key constants in the WdKey enumeration table except for the modifier keys since they cannot be used alone. Then merge any contiguous ranges, so the Select Case is easier to read.

Final main key validation function

Putting the steps together, we get our relatively short function to validate a make for a keyboard shortcut.

Function IsValidKeyBindingMainKey(Key As Long) As Boolean
' Return whether the indicated Key constant can be used as a main key when keybinding
' Modifier keys are excluded
' Arrow keys vbKeyLeft To vbKeyDown are from VBA keycode table
' All other keys are from the WdKey enumeration

' Test versus explicit WdKey and VBA keycode constants (as of September 2024)
Select Case Key
Case vbKeyLeft To vbKeyDown, wdKey0 To wdKey9, wdKeyA To wdKeyZ, _
wdKeyF1 To wdKeyF16, wdKeySemiColon To wdKeyBackSingleQuote, _
wdKeySpacebar To wdKeyHome, wdKeyOpenSquareBrace To wdKeySingleQuote,
wdKeyNumeric0 To wdKeyNumericAdd, _
wdKeyNumericSubtract To wdKeyNumericDivide, _
wdKeyBackspace, wdKeyDelete, wdKeyTab, wdKeyReturn, wdKeyScrollLock, _
wdKeyTab, wdKeyNumeric5Special, wdKeyEsc, wdKeyPause
' Found valid main key constant
IsValidKeyBindingMainKey = True

' Forward Delete key is valid in Word for Windows but invalid in Word for Mac
' beyond standard actions since wdKeyDelete is not defined in Word for Mac
#If Not Mac Then
' Case is only included in Word for Windows
Case wdKeyDelete:
IsValidIsolatedMainKey = True
#End If

Case Else
' Default to an invalid main key
IsValidKeyBindingMainKey = False
End Select
End Function

This is another function where it’s just messier to include the long Select Case statement in the main function. Of course, add or remove valid key constants as you prefer.

This uses the WdKey enumeration constants for the Case evaluations which is a little safer than using explicit number values. It makes the statement rather long, but I think that is preferable to manually listing (called “hard coding”) the constants as numbers. This doesn’t protect us entirely from any changes to the constant tables because a range might not be preserved in order, but at least it’s easier to read in terms understanding what is being tested.

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.