Keyboard Handling, Mnemonics, and ISimpleFrameSite

As we saw in Chapter 22, in-place activation by itself allows keyboard accelerators to work only for the current UI-active object. While this is great for content objects, it doesn't work well for controls. When a form has multiple controls that are all in-place active, pressing keyboard mnemonics—Alt key combinations among others—should activate an appropriate control regardless of whether it's UI active.

To accommodate this need, both controls and containers require special handling. Controls have to make special considerations inside their implementation of IOleInPlaceActiveObject::TranslateAccelerator. Instead of simply processing its own accelerators, a control should decide whether it wants priority for the keystroke or whether it wants to give the container the chance first. When a control wants priority for a keystroke, it processes that keystroke first. If it wants to give the container priority, it calls IOleControlSite::TranslateAccelerator and then handles the keystroke if the container does not. For example, an edit control might want priority for a key combination such as Ctrl+C (Edit Copy) but might give the container priority for the Tab key. Because interpretations of specific keys vary from control to control, this mechanism allows the control to do what makes the most sense.

Now, of course, IOleInPlaceActiveObject::TranslateAccelerator applies only to the control that is UI active—the control with the focus. Mnemonics, however, have to apply to all controls in the form simultaneously, and the container is responsible for finding the right control to notify for any keystroke. A control must therefore tell the container what keystrokes it wants. This is the purpose of IOleControl::GetControlInfo, which returns a CONTROLINFO structure to the container:


struct CONTROLINFO:
{
ULONG cb; //Structure size
HACCEL hAccel; //Mnemonics table for the control
USHORT cAccel; //Number of mnemonics
DWORD dwFlags; //CTRLINFO_* flags
};

#define CTRLINFO_EATS_RETURN 1 //Control processes RETURN key
#define CTRLINFO_EATS_ESCAPE 2 //Control processes ESC key

This structure contains a Windows accelerator table that describes the keystrokes a control wants to process.5 The CTRLINFO_* flags let a control tell the container whether it processes the Enter or Esc key when the control has the focus, which is important for default and cancel button handling, as described later in "Default and Cancel Buttons." A control is allowed to change its mnemonics at run time, so it calls IOleControlSite::OnControlInfoChanged to tell the container to reload the data if necessary.

When the user presses any key, the container first sends that keystroke to the current UI-active control. Otherwise, the container receives the WM_KEYDOWN or WM_SYSKEYDOWN message itself. The container can choose to eat certain keys itself, which usually involves the Enter and Esc keys. When the container doesn't use a key itself or doesn't choose to block it, the container searches each control's mnemonics table for a match. The order in which this happens is entirely the container's decision. When a match is found, the container calls the appropriate control's IOleControl::OnMnemonic, and the control then does whatever is appropriate for that keystroke.

There are, of course, some special cases for handling mnemonics. First of all, a control marked with OLEMISC_ACTSLIKELABEL doesn't process its own mnemonics. When a label's mnemonic matches a keystroke, the container should set the focus to (activate the UI of) whichever control is attached to that label in the form. (By "attached," I mean that the label gives a name to the other control.) This implies a container-managed order to the controls on the form other than the z-order (which handles only overlapping controls).

A user generally expects that the Tab key will move the focus from control to control, usually according to the visible order of the controls themselves, ignoring those marked OLEMISC_INVISIBLEATRUNTIME, OLEMISC_SIMPLEFRAME, OLEMISC_ACTSLIKELABEL, and OLEMISC_NOUIACTIVATE (for example, icons and pictures). Each control that can be tabbed to is called a tab stop. Usually, buttons, check boxes, list boxes, edit controls, combo boxes, and the like are tab stops.

When tabbing through the controls on a form you must pay attention to sets of exclusive buttons, such as radio buttons, a type of control in which only one button in the group can be checked at any given time. The Tab key navigates to this group as a single unit, and another Tab keystroke moves the focus to the control after the group. Inside the group, the arrow keys and the spacebar are used to change the selected option. The container is responsible for determining where the group starts and stops and is responsible for ensuring that only one button in the group is checked. The container determines whether any given control is an exclusive button by checking whether the type of the object's DISPID_VALUE property is OLE_OPTEXCLUSIVE.6 With this information, it can determine where the group starts and stops in order to handle it appropriately. The container can also have its own user interface for assigning such boundaries.

When processing Tab keystrokes, a container must also take into account controls marked OLEMISC_SIMPLEFRAME. A simple frame control is one that groups other controls together—the Windows group box control is an example of this. A simple frame has its own window in the container and creates a group boundary so that the group acts as a single tab stop. When a simple frame receives Windows messages, it must pass those messages to the container through the interface ISimpleFrameSite:


interface ISimpleFrameSite : IUnknown
{
PreMessageFilter(HWND hWnd, UINT iMsg, WPARAM wp, LPARAM lp,
LRESULT *plResult, DWORD *pdwCookie);
PostMessageFilter(HWND hWnd, UINT iMsg, WPARAM wp, LPARAM lp,
LRESULT *plResult, DWORD dwCookie);
}

When the simple frame receives a message, it calls ISimpleFrameSite::PreMessageFilter. The container does what it wants with the message, stores a return value for the message in *plResult, and stores some other piece of information (such as a pointer to a data structure) in *pdwCookie. If the container returns S_FALSE from PreMessageFilter, the control should not process the message itself. Otherwise, the simple frame can perform its own processing of the message, after which it calls ISimpleFrameSite::PostMessageFilter. The control passes to this function the same cookie that was returned from PreMessageFilter, allowing the container to pass information through the entire sequence of calls.

The container responds to the messages it receives in a variety of ways. The container might check for special keystrokes, implement its Tab key behavior, handle arrow keys in a special way, process WM_PAINT messages, and so forth. This chapter will not explore the uses of ISimpleFrameSite, so consult the CDK documentation for more information.

5 An accelerator table is an array of ACCEL structures contained in global memory and is identified with the hAccel field in CONTROLINFO. The control always maintains ownership of this memory. The container should never attempt to free the memory itself.

6 The OLE_OPTEXCLUSIVE type has an identity of GUID_OPTIONVALUEEXCLUSIVE as defined in the OLE CDK header files. A container has to retrieve the type of the property and then retrieve the GUID for that type using ITypeInfo member functions. Along a similar line, OLE Controls defines the type OLE_TRISTATE for the handling of the selection state of check boxes.