PART II  READING INPUT
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Chapter 3  The Keyboard
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Like most interactive programs that run on personal computers, Windows
applications rely heavily on the keyboard for user input. Although Windows
also supports a mouse as an input device, you can't depend on a mouse being
present in an installed version of Windows. For this reason, program
developers should attempt to allow complete program functionality from the
keyboard. (Of course, in some cases, such as drawing programs or desktop
publishing programs, this is simply not practical and a mouse will be
required.)

The keyboard cannot be treated solely as an input device in isolation from
other program functions. For example, a program almost always echoes the
keyboard input by displaying typed characters in the client area of a
window. Handling keyboard input and displaying text must be treated
together. Sometimes the keystrokes result in a document being created that
is eventually saved in a disk file. Sometimes a program requires that the
user enter an MS-DOS filename. These apparently straightforward chores raise
issues related to the support of the ASCII extended character set (codes of
128 and above) and of international characters. For this reason, topics such
as the character sets supported by Windows and multibyte character codes are
also covered in this chapter.

KEYBOARD BASICS

As the user presses and releases keys, the keyboard driver passes the
keystrokes to Windows. Windows saves the keystrokes in the system message
queue and then transfers them to the message queue of the program with the
"input focus." These messages are processed in the program's window
procedure. In most cases, the keyboard information encoded in these messages
is probably more than your program needs. Part of the job of handling the
keyboard is knowing which messages are important and which are not.

The Keyboard Driver

Windows is shipped with several keyboard drivers for the support of various
keyboard hardware and dynamic link libraries that support international
keyboard configurations. Keyboards for European languages must include
additional characters (such as letters with diacritics) and symbols (such as
the British pound sign). When you install Windows, the SETUP program copies
the keyboard driver for the keyboard and country you request into the SYSTEM
subdirectory of your Windows directory.

KEYBOARD.DRV is a relatively small and simple driver. When Windows starts
up, it enables the keyboard driver, which responds by saving the original
interrupt vector addresses for Interrupt 09H (the hardware keyboard
interrupt) and setting this interrupt vector to routines within the driver.

Pressing or releasing a key generates an Interrupt 09H. This is sometimes
called an "asynchronous" interrupt because it can occur at any time. The
interrupt suspends the program currently running and passes control to the
Interrupt 09H keyboard handler. When the keyboard handler is finished, it
passes control back to the interrupted program. The Interrupt 09H keyboard
handler within KEYBOARD.DRV decodes the key and calls a routine within the
Windows USER module, which stores them as queued messages. The Windows
program then obtains the keyboard messages when the program calls
GetMessage.

Because a Windows program effectively polls for keyboard input by calling
GetMessage, Windows programs are not very different from PC programs that
obtain keystrokes by polling through the software Interrupts 16H and 21H.
However, the quantity of information that Windows encodes in the keyboard
messages is much greater than that available from Interrupts 16H and 21H.

Some application programs written for the IBM PC intercept Interrupt 09H and
do their own hardware keyboard processing. This allows the program to use
all possible combinations of keystrokes, not only those defined by the PC
BIOS. Windows programs are not very different from these programs either,
because the window procedure is a message handler that receives messages
about all keyboard events. The only real difference between message handling
and interrupt handling is that the Windows messages are not asynchronous. A
Windows program is never interrupted to be notified of a keystroke; the
program receives a new keyboard message only from the message queue. In
short, Windows provides programs with all the benefits of intercepting the
hardware Interrupt 09H but with none of the hassles.

When a user types on the keyboard faster than a program can process the
keys, Windows stores the extra keystrokes in a system message queue rather
than in an individual program's message queue. One of these extra keystrokes
(Alt-Tab, for instance) may have the effect of switching to another program.
The keys following Alt-Tab should then go to the other program. Windows
correctly synchronizes such keyboard messages.

Windows sends eight different messages to programs to indicate various
keyboard events. That may seem like a lot, but your program can safely
ignore many of them.


Ignoring the Keyboard

Although the keyboard is the primary source of user input to Windows
programs, your program does not need to act on every keyboard message it
receives. Windows handles many keyboard functions itself. For instance, you
can ignore keystrokes that pertain to system functions. These keystrokes
generally involve the Alt key.

A program need not monitor these keystrokes itself because Windows notifies
a program of the effect of the keystrokes. (A program can monitor the
keystrokes if it wants to, however.) For instance, if the Windows user
selects a menu item with the keyboard, Windows sends the program a message
that the menu item has been selected, regardless of whether it was selected
by using the mouse or by using the keyboard. (Menus are covered in Chapter
9.)

Some Windows programs use "keyboard accelerators" to invoke common menu
items. The accelerators generally involve the function keys, special
noncharacter keys such as Insert or Delete, or a letter in combination with
the Ctrl key. These keyboard accelerators are defined in a program's
resource script. (Chapter 9 shows how Windows translates the accelerators
into menu command messages. You don't have to do the translation yourself.)

Dialog boxes (covered in Chapter 10) also have a keyboard interface, but
programs usually do not need to monitor the keyboard when a dialog box is
active. The keyboard interface is handled by Windows, and Windows sends
messages to your program about the effects of the keystrokes. Dialog boxes
can contain "edit" controls for text input. These are generally small boxes
in which the user types a character string. Windows handles all the edit
control logic and gives your program the final contents of the edit control
when the user is done.

Even within your main window you can define child windows that function as
edit controls. An extreme example of this is the Windows NOTEPAD program,
which is little more than a large multiline edit control. NOTEPAD does
little keyboard processing on its own and relies on Windows to handle all
the dirty work. (Chapter 6 discusses how this works.)


Focus, Focus, Who's Got the Focus?

The keyboard must be shared by all applications running under Windows. Some
applications may have more than one window, and the keyboard must be shared
by these windows within the same application. When a key on the keyboard is
pressed, only one window procedure can receive a message that the key has
been pressed. The window that receives this keyboard message is the window
with the "input focus."

The concept of input focus is closely related to the concept of "active
window." The window with the input focus is either the active window or a
child window of the active window. The active window is usually easy to
identify. If the active window has a caption bar, Windows highlights the
caption bar. If the active window has a dialog frame (a form most commonly
seen in dialog boxes) instead of a caption bar, Windows highlights the
frame. If the active window is an icon, Windows highlights the window's
caption bar text below the icon.

The most common child windows are controls such as push buttons, radio
buttons, check boxes, scroll bars, edit boxes, and list boxes that usually
appear in a dialog box. Child windows are never themselves active windows.
If a child window has the input focus, then the active window is its parent.
Child window controls indicate that they have the input focus generally by
using a flashing cursor or caret.

If the active window is an icon, then no window has the input focus. Windows
continues to send keyboard messages to the icon, but these messages are in a
different form from keyboard messages sent to active windows that are not
icons.

A window procedure can determine when it has the input focus by trapping
WM_SETFOCUS and WM_KILLFOCUS messages. WM_SETFOCUS indicates that the window
is receiving the input focus, and WM_KILLFOCUS signals that the window is
losing the input focus.


Keystrokes and Characters

The messages that an application receives from Windows about keyboard events
distinguish between "keystrokes" and "characters." This is in accordance
with the two ways you can view the keyboard. First, you can think of the
keyboard as a collection of keys. The keyboard has only one A key. Pressing
that key is a keystroke. Releasing that key is a keystroke. But the keyboard
is also an input device that generates displayable characters. The A key can
generate several characters depending on the status of the Ctrl, Shift, and
Caps Lock keys. Normally, the character is a lowercase a. If the Shift key
is down or Caps Lock is toggled on, the character is an uppercase A. If Ctrl
is down, the character is a Ctrl-A. On a foreign-language keyboard, the A
keystroke may be preceded by a "dead-character key" or by Shift, Ctrl, or
Alt in various combinations. The combinations could generate a lowercase a
or an uppercase A with an accent mark.

For keystroke combinations that result in displayable characters, Windows
sends a program both keystroke messages and character messages. Some keys do
not generate characters. These include the shift keys, the function keys,
the cursor movement keys, and special keys such as Insert and Delete. For
these keys, Windows generates only keystroke messages.



KEYSTROKE MESSAGES

When you press a key, Windows places either a WM_KEYDOWN or WM_SYSKEYDOWN
message in the message queue of the window with the input focus. When you
release a key, Windows places either a WM_KEYUP or WM_SYSKEYUP message in
the message queue.


                      Key Pressed    Key Released
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Nonsystem Keystroke:  WM_KEYDOWN     WM_KEYUP
System Keystroke:     WM_SYSKEYDOWN  WM_SYSKEYUP

Usually the "down" and "up" messages occur in pairs. However, if you hold
down a key so that the typematic (autorepeat) action takes over, Windows
sends the window procedure a series of WM_KEYDOWN (or WM_SYSKEYDOWN)
messages and a single WM_KEYUP (or WM_SYSKEYUP) message when the key is
finally released. Like all messages, keystroke messages are time-stamped.
You can obtain the relative time a key was pressed or released by calling
GetMessageTime.

System and Nonsystem Keystrokes

The "SYS" in WM_SYSKEYDOWN and WM_SYSKEYUP stands for "system" and refers to
keystrokes that are more important to Windows than to the Windows
application. The WM_SYSKEYDOWN and WM_SYSKEYUP messages are usually
generated for keys typed in combination with the Alt key. These keystrokes
invoke options on the program's menu or system menu, or they are used for
system functions such as switching the active window (Alt-Tab or Alt-Esc) or
for system menu accelerators (Alt in combination with a function key).
Programs usually ignore the WM_SYSKEYUP and WM_SYSKEYDOWN messages and pass
them to DefWindowProc. Because Windows takes care of all the Alt-key logic,
you really have no need to trap these messages. Your window procedure will
eventually receive other messages concerning the result of these keystrokes
(such as a menu selection). If you want to include code in your window
procedure to trap the system keystroke messages (as we will do in the
KEYLOOK program later in this chapter), pass the messages to DefWindowProc
after you process them so that Windows can still use them for their normal
purposes.

But think about this for a moment. Almost everything that affects your
program's window passes through your window procedure first. Windows does
something with the message only if you pass the message to DefWindowProc.
For instance, if you add the lines:

case WM_SYSKEYDOWN :
case WM_SYSKEYUP :
case WM_SYSCHAR :
     return 0 ;

to a window procedure, then you effectively disable all Alt-key operations
(menu commands, Alt-Tab, Alt-Esc, and so on) when your program has the input
focus. Although I doubt you would want to do this, I trust you're beginning
to sense the power in your window procedure.

The WM_KEYDOWN and WM_KEYUP messages are usually generated for keys that are
pressed and released without the Alt key. Your program may use or discard
these keystroke messages. Windows itself doesn't care about them.


The lParam Variable

For all four keystroke messages, the 32-bit lParam variable passed to the
window procedure is divided into six fields: Repeat Count, OEM Scan Code,
Extended Key Flag, Context Code, Previous Key State, and Transition State.
(See Figure 3-1.)

  (Figure 3-1. may be found in the printed book.)

Repeat Count

The Repeat Count is the number of keystrokes represented by the message. In
most cases the Repeat Count is set to 1. However, if a key is held down and
your window procedure is not fast enough to process key-down messages at the
typematic rate (approximately  a 10-character-per-second default), Windows
combines several WM_KEYDOWN or WM_SYSKEYDOWN messages into a single message
and increases Repeat Count accordingly. The Repeat Count is always 1 for a
WM_KEYUP or WM_SYSKEYUP message.

Because a Repeat Count greater than 1 indicates that typematic keystrokes
are occurring faster than your program can process them, you may want to
ignore the Repeat Count when processing the keyboard messages. Almost
everyone has had the experience of "overscrolling" a word-processing
document or spreadsheet because extra keystrokes have stacked up in the
keyboard buffer. Ignoring the Repeat Count in your program will
significantly reduce the possibilities for overscrolling. However, in other
cases you will want to use the Repeat Count. You should probably try your
programs both ways and see which approach feels the most natural.


OEM Scan Code

The OEM Scan Code is the keyboard scan code generated by the hardware of the
computer. For the IBM PC, this scan code is the same as the value passed
back to a program in register AH during a BIOS Interrupt 16H call. Windows
applications generally ignore the OEM Scan Code because there are better
ways to decode keyboard information.


Extended Key Flag

The Extended Key Flag is 1 if the keystroke results from one of the
additional keys on the IXC Enhanced Keyboard. (The IXC Enhanced Keyboard has
function keys across the top and a separate [combined] keypad for cursor
keys and number keys.) This flag is set to 1 for the Alt and Ctrl keys at
the right of the keyboard, the cursor movement keys (including Insert and
Delete) that are not part of the numeric keypad, the Slash (/) and Enter
keys on the numeric keypad, and the Num Lock key. Windows programs generally
ignore the Extended Key Flag.


Context Code

The Context Code is 1 if the Alt key is pressed. This bit will always be 1
for the WM_SYSKEYUP and WM_SYSKEYDOWN messages and 0 for the WM_KEYUP and
WM_KEYDOWN messages with two exceptions:

  þ   If the active window is an icon, it does not have the input focus. All
      keystrokes generate WM_SYSKEYUP and WM_SYSKEYDOWN messages. If the Alt
      key is not pressed, the Context Code field is set to 0. (Windows uses
      SYS keyboard messages so that the active window that is an icon
      doesn't process these keystrokes.)

  þ   On some foreign-language keyboards, certain characters are generated
      by combining Shift, Ctrl, or Alt with another key. In these cases the
      lParam variable that accompanies WM_KEYUP and WM_KEYDOWN messages has
      a 1 in the Context Code field, but the messages are not system
      keystroke messages.


Previous Key State

The Previous Key State is 0 if the key was previously up and 1 if the key
was previously down. It is always set to 1 for a WM_KEYUP or WM_SYSKEYUP
message, but it can be 0 or 1 for a WM_KEYDOWN or WM_SYSKEYDOWN message. A 1
indicates second and subsequent messages for keys that are the result of
typematic action.


Transition State

The Transition State is 0 if the key is being pressed and 1 if the key is
being released. The field is set to 0 for a WM_KEYDOWN or WM_SYSKEYDOWN
message and to 1 for a WM_KEYUP or WM_SYSKEYUP.



Virtual Key Codes

Although some information in lParam might be useful for processing WM_KEYUP,
WM_KEYDOWN, WM_SYSKEYUP, and WM_SYSKEYDOWN messages, the wParam parameter is
much more important. This parameter contains the "virtual key code" that
identifies the key that was pressed or released. The developers of Windows
have attempted to

Table   VIRTUAL KEY CODES


                 WINDOWS.H
Decimal  Hex     Identifier     Required  IBM Keyboard
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
1        01      VK_LBUTTON

2        02      VK_RBUTTON

3        03      VK_CANCEL      *         Ctrl-Break

4        04      VK_MBUTTON

8        08      VK_BACK        *         Backspace

9        09      VK_TAB         *         Tab

12       0C      VK_CLEAR                 Numeric keypad 5 with Num Lock
                                          OFF

13       0D      VK_RETURN      *         Enter

16       10      VK_SHIFT       *         Shift

17       11      VK_CONTROL     *         Ctrl

18       12      VK_MENU        *         Alt

19       13      VK_PAUSE                 Pause

20       14      VK_CAPITAL     *         Caps Lock

27       1B      VK_ESCAPE      *         Esc

32       20      VK_SPACE       *         Spacebar

33       21      VK_PRIOR       *         Page Up

34       22      VK_NEXT        *         Page Down

35       23      VK_END                   End

36       24      VK_HOME        *         Home

37       25      VK_LEFT        *         Left Arrow

38       26      VK_UP          *         Up Arrow

39       27      VK_RIGHT       *         Right Arrow

40       28      VK_DOWN        *         Down Arrow

41       29      VK_SELECT

42       2A      VK_PRINT

43       2B      VK_EXECUTE

44       2C      VK_SNAPSHOT              Print Screen

45       2D      VK_INSERT      *         Insert

46       2E      VK_DELETE      *         Delete

47       2F      VK_HELP

48\-57   30\-39                 *         0 through 9 on main keyboard

65\-90   41\-5A                 *         A through Z

96       60      VK_NUMPAD0               Numeric keypad 0 with Num Lock
                                          ON



97       61      VK_NUMPAD1               Numeric keypad 1 with Num Lock
                                          ON

98       62      VK_NUMPAD2               Numeric keypad 2 with Num Lock
                                          ON

99       63      VK_NUMPAD3               Numeric keypad 3 with Num Lock
                                          ON

100      64      VK_NUMPAD4               Numeric keypad 4 with Num Lock
                                          ON

101      65      VK_NUMPAD5               Numeric keypad 5 with Num Lock
                                          ON

102      66      VK_NUMPAD6               Numeric keypad 6 with Num Lock
                                          ON

103      67      VK_NUMPAD7               Numeric keypad 7 with Num Lock
                                          ON

104      68      VK_NUMPAD8               Numeric keypad 8 with Num Lock
                                          ON

105      69      VK_NUMPAD9               Numeric keypad 9 with Num Lock
                                          ON

106      6A      VK_MULTIPY               Numeric keypad * (enhanced
                                          keyboard)

107      6B      VK_ADD                   Numeric keypad + (enhanced
                                          keyboard)

108      6C      VK_SEPARATOR

109      6D      VK_SUBTRACT              Numeric keypad - (enhanced
                                          keyboard)

110      6E      VK_DECIMAL               Numeric keypad

111      6F      VK_DIVIDE                Numeric keypad /(enhanced
                                          keyboard)

112      70      VK_F1          *         Function key F1

113      71      VK_F2          *         Function key F2

114      72      VK_F3          *         Function key F3

115      73      VK_F4          *         Function key F4

116      74      VK_F5          *         Function key F5

117      75      VK_F6          *         Function key F6

118      76      VK_F7          *         Function key F7

119      77      VK_F8          *         Function key F8

120      78      VK_F9          *         Function key F9

121      79      VK_F10         *         Function key F10

122      7A      VK_F11                   Function key F11 (enhanced
                                          keyboard)

123      7B      VK_F12                   Function Key F12 (enhanced
                                          keyboard)

124      7C      VK_F13



125      7D      VK_F14

126      7E      VK_F15

127      7F      VK_F16

144      90      VK_NUMLOCK               Num Lock





define virtual keys in a device-independent manner. For this reason, some
virtual key codes cannot be generated on the IBM PC and strict compatibles
but may be found on other manufacturer's keyboards.

The virtual key codes you use most often have names defined in WINDOWS.H.
The table above shows these names along with the numeric key codes and the
IBM PC key that corresponds to the virtual key. Although all keys cause
keystroke messages, the table does not include any symbol keys (such as the
key with the / and ? symbols). These keys have virtual key codes of 128 and
above, and they are often defined differently for international keyboards.
You can determine the values of these virtual key codes using the KEYLOOK
program that is shown later in this chapter, but normally you should not
process keystroke messages for these keys.

An asterisk (*) in the column labeled "Required" indicates that the key is
mandatory for any Windows implementation. Windows also requires that a
keyboard and keyboard driver allow the Shift, Ctrl, and Shift and Ctrl keys
together to be combined with all letter keys, all required cursor keys, and
all required function keys. The VK_LBUTTON, VK_MBUTTON, and VK_RBUTTON
virtual key codes refer to the left, middle, and right buttons of a mouse.
However, you will never receive keystroke messages with wParam set to these
values. The mouse generates its own messages.


Shift States

The wParam and lParam parameters that accompany WM_KEYDOWN, WM_KEYUP,
WM_SYSKEYDOWN, and WM_SYSKEYUP messages do not tell your program about the
state of the shift keys. You can obtain the current state of any virtual key
using the GetKeyState function. This function generally is used to obtain
the state of shift keys (Shift, Ctrl, and Alt) and toggle keys (Caps Lock
and Num Lock). For instance:

GetKeyState (VK_SHIFT) ;

returns a negative value (that is, the high bit is set) if the Shift key is
down. The value returned from:

GetKeyState (VK_CAPITAL) ;

has the low bit set if the Caps Lock key is toggled on. You can also obtain
the state of the mouse buttons using the virtual key codes VK_LBUTTON,
VK_RBUTTON, and VK_MBUTTON. However, most Windows programs that need to
monitor a combination of mouse buttons and keystrokes usually do it the
other way around--by checking keystrokes when they receive a mouse message.
In fact, shift-state information is included in the mouse messages (as
you'll see in the next chapter).

Be careful with GetKeyState. It is not a real-time keyboard status check.
Rather, it is a check of the keyboard status up to and including the current
message being processed. GetKeyState does not let you retrieve keyboard
information independent of normal keyboard messages. For instance, you may
want to hold up processing in your window procedure until the user presses
the F1 function key:

while (GetKeyState (VK_F1) >= 0) ;  // WRONG !!!

This statement will execute for a very long time--until you reset your
machine with Ctrl-Alt-Delete. Your program must retrieve the keyboard
message from the queue before GetKeyState can retrieve the state of the key.
This synchronization actually works to your advantage, because if you need
to know the shift state for a particular keystroke message, GetKeyState is
guaranteed to be accurate, even if you are processing the message after the
shift key has been released. If you really need the current state of the
key, you can use GetAsyncKeyState.


Using Keystroke Messages

The idea of a program getting information about every keystroke is certainly
nice, but most Windows programs ignore all but a few keystroke messages. The
WM_SYSKEYDOWN and WM_SYSKEYUP messages are for Windows system functions, and
you don't need to look at them. If you process WM_KEYDOWN messages, you can
also ignore WM_KEYUP messages.

Windows programs generally use WM_KEYDOWN messages for keystrokes that do
not generate characters. Although you may think that it's possible to use
keystroke messages in combination with shift-state information to translate
keystroke messages into character messages, don't do it. You'll have
problems with international keyboard differences. For instance, if you get a
WM_KEYDOWN message with wParam equal to 33H, you know the user pressed the 3
key. So far, so good. If you use GetKeyState and find out that the Shift key
is down, you might assume that the user is typing a pound sign (#). Not
necessarily so. A British user is typing a œ. So the WM_KEYDOWN messages are
most useful for the cursor movement keys, the function keys, and special
keys such as Insert and Delete. However, Insert, Delete, and the function
keys often appear as menu accelerators. Because Windows translates menu
accelerators into menu command messages, you don't have to process the
keystrokes themselves. Some non-Windows programs for the PC use function
keys extensively in combination with the Shift, Ctrl, and Alt keys. You can
do something similar in your Windows programs, but it's not recommended. If
you want to use the function keys, they should duplicate menu commands. One
objective in Windows is to provide a user interface that doesn't require
memorizing or using complex command charts.

We've managed to eliminate everything except one final case: Most of the
time, you will process WM_KEYDOWN messages only for cursor movement keys.
When you use the cursor keys, you can check the Shift-key and Ctrl-key
states through GetKeyState. Windows functions often use the Shift key in
combination with the cursor keys to extend a selection in (for instance) a
word-processing document. The Ctrl key is often used to alter the meaning of
the cursor key. (For example, Ctrl in combination with the Right Arrow key
might mean to move the cursor one word to the right.)

The Common User Access: Advanced Interface Design Guide contains a list of
recommended keyboard definitions. (The guide, hereinafter referred to as the
CUA Advanced Interface Design Guide, is included in the Windows Software
Development Kit and is part of the IBM Systems Application Architecture
Library.) You can also examine how the keyboard is used in existing Windows
programs. If you don't like those definitions, you are free to do something
different. But keep in mind that doing so may be detrimental to a user's
ability to quickly learn your program.



ENHANCING SYSMETS:

ADDING A KEYBOARD INTERFACE

When we wrote the three versions of the SYSMETS program in Chapter 2, we
didn't know anything about the keyboard. We were able to scroll the text
only by using the mouse on the scroll bars. Now that we know how to process
keystroke messages, let's add a keyboard interface to SYSMETS. This is
obviously a job for cursor movement keys. We'll use most of the cursor
movement keys (Home, End, Page Up, Page Down, Up Arrow, and Down Arrow) for
vertical scrolling. The Left Arrow key and the Right Arrow key can take care
of the less-important horizontal scrolling.

Adding WM_KEYDOWN Logic

One obvious way to create a keyboard interface is to add some WM_KEYDOWN
logic to the window procedure that parallels the WM_VSCROLL and WM_HSCROLL
logic:

case WM_KEYDOWN :
     nVscrollInc = nHscrollInc = 0 ;

     switch (wParam)
          {
          case VK_HOME :       // same as WM_VSCROLL, SB_TOP
               nVscrollInc = -nVscrollPos ;
               break ;

          case VK_END :        // same as WM_VSCROLL, SB_BOTTOM
               nVscrollInc = nVscrollMax - nVscrollPos ;
               break ;

          case VK_UP :         // same as WM_VSCROLL, SB_LINEUP
               nVscrollInc = -1 ;
               break ;

          case VK_DOWN :       // same as WM_VSCROLL, SB_LINEDOWN
               nVscrollInc = 1 ;
               break ;

          case VK_PRIOR :      // same as WM_VSCROLL, SB_PAGEUP
               nVscrollInc = min (-1, -cyClient / cyChar) ;
               break ;

          case VK_NEXT :       // same as WM_VSCROLL, SB_PAGEDOWN
               nVscrollInc = max (1, cyClient / cyChar) ;
               break ;

          case VK_LEFT :       // same as WM_HSCROLL, SB_PAGEUP
               nHscrollInc = -8 ;
               break ;

          case VK_RIGHT :      // same as WM_HSCROLL, SB_PAGEDOWN
               nHscrollInc = 8 ;
               break ;

          default :
               break ;
          }

     if (nVscrollInc = max (-nVscrollPos,
               min (nVscrollInc, nVscrollMax - nVscrollPos)))
          {
          nVscrollPos += nVscrollInc ;
          ScrollWindow (hwnd, 0, -cyChar * nVscrollInc, NULL, NULL) ;
          SetScrollPos (hwnd, SB_VERT, nVscrollPos, TRUE) ;
          UpdateWindow (hwnd) ;
          }

     if (nHscrollInc = max (-nHscrollPos,
               min (nHscrollInc, nHscrollMax - nHscrollPos)))
          {
          nHscrollPos += nHscrollInc ;
          ScrollWindow (hwnd, -cxChar * nHscrollInc, 0, NULL, NULL) ;
          SetScrollPos (hwnd, SB_HORZ, nHscrollPos, TRUE) ;
          }

     return 0 ;

Do you dislike this code as much as I do? Simply duplicating all the scroll
bar code is unwise, because if we ever wanted to change the scroll bar
logic, we'd have to make parallel changes in WM_KEYDOWN. There has to be a
better way. And there is.


Sending Messages

Wouldn't it be better to simply translate each of these WM_KEYDOWN messages
into an equivalent WM_VSCROLL and WM_HSCROLL message and then perhaps fool
WndProc into thinking that it's getting a WM_VSCROLL or WM_HSCROLL message,
perhaps by sending a phony scroll bar message to the window procedure?
Windows lets you do this. The function is called SendMessage, and it takes
the same parameters as those passed to the window procedure:

SendMessage (hwnd, message, wParam, lParam) ;

When you call SendMessage, Windows calls the window procedure whose window
handle is hwnd, passing to it these four parameters. When the window
procedure has completed processing the message, Windows returns control to
the next statement following the SendMessage call. The window procedure to
which you send the message could be the same window procedure, another
window procedure in the same program, or a window procedure in another
application.

Here's how we might use SendMessage for processing WM_KEYDOWN codes in the
SYSMETS program:

case WM_KEYDOWN :
     switch (wParam)
          {
          case VK_HOME :
               SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L) ;
               break ;

          case VK_END :
               SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L) ;
               break ;

          case VK_PRIOR :
               SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0L) ;
               break ;
[other program lines]

OK, you get the general idea. Our goal was to add a keyboard interface to
the scroll bars, and that's exactly what we've done. We've made the cursor
movement keys duplicate scroll bar logic by actually sending the window
procedure a scroll bar message. Now you see why I included SB_TOP and
SB_BOTTOM processing for WM_VSCROLL messages in the SYSMETS3 program. It
wasn't used then, but it's used now for processing the Home and End keys.
The final SYSMETS program, shown in Figure 3-2, incorporates these changes.
You'll also need the SYSMETS.H file from Chapter 2 (Figure 2-4) to compile
this program.

Remember: To send a message to a window procedure, use the SendMessage
function. Do not try to call the window procedure directly like this:

WndProc (hwnd, WM_VSCROLL, SB_PAGEUP, 0L) ; // WRONG !!!

This statement will cause "unpredictable results" (if you call a system
crash "unpredictable"). You may define and call other subroutines within a
Windows program, but you must not call a window procedure directly. You'll
find out why in Chapter 7.

 SYSMETS.MAK

#-----------------------
# SYSMETS.MAK make file
#-----------------------

sysmets.exe : sysmets.obj sysmets.def
     link sysmets, /align:16, NUL, /nod slibcew libw, sysmets
     rc sysmets.exe

sysmets.obj : sysmets.c sysmets.h
     cl -c -Gsw -Ow -W2 -Zp sysmets.c

 SYSMETS.C

/*-----------------------------------------------------
   SYSMETS.C -- System Metrics Display Program (Final)
                (c) Charles Petzold, 1990
  -----------------------------------------------------*/

#include 
#include "sysmets.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "SysMets" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;



          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "System Metrics",
                          WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static short  cxChar, cxCaps, cyChar, cxClient, cyClient, nMaxWidth,
                   nVscrollPos, nVscrollMax, nHscrollPos, nHscrollMax ;
     char          szBuffer[10] ;
     HDC           hdc ;
     short         i, x, y, nPaintBeg, nPaintEnd, nVscrollInc, nHscrollInc ;
     PAINTSTRUCT   ps ;
     TEXTMETRIC    tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;

               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;

               nMaxWidth = 40 * cxChar + 18 * cxCaps ;
               return 0 ;

          case WM_SIZE :
               cyClient = HIWORD (lParam) ;
               cxClient = LOWORD (lParam) ;
               nVscrollMax = max (0, NUMLINES + 2 - cyClient / cyChar) ;
               nVscrollPos = min (nVscrollPos, nVscrollMax) ;

               SetScrollRange (hwnd, SB_VERT, 0, nVscrollMax, FALSE) ;
               SetScrollPos   (hwnd, SB_VERT, nVscrollPos, TRUE) ;

               nHscrollMax = max (0, 2 + (nMaxWidth - cxClient) / cxChar) ;
               nHscrollPos = min (nHscrollPos, nHscrollMax) ;

               SetScrollRange (hwnd, SB_HORZ, 0, nHscrollMax, FALSE) ;
               SetScrollPos   (hwnd, SB_HORZ, nHscrollPos, TRUE) ;
               return 0 ;

          case WM_VSCROLL :
               switch (wParam)
                    {
                    case SB_TOP :
                         nVscrollInc = -nVscrollPos ;
                         break ;

                    case SB_BOTTOM :
                         nVscrollInc = nVscrollMax - nVscrollPos ;
                         break ;

                    case SB_LINEUP :
                         nVscrollInc = -1 ;
                         break ;

                    case SB_LINEDOWN :
                         nVscrollInc = 1 ;
                         break ;

                    case SB_PAGEUP :
                         nVscrollInc = min (-1, -cyClient / cyChar) ;
                         break ;

                    case SB_PAGEDOWN :
                         nVscrollInc = max (1, cyClient / cyChar) ;
                         break ;

                    case SB_THUMBTRACK :
                         nVscrollInc = LOWORD (lParam) - nVscrollPos ;
                         break ;

                    default :
                         nVscrollInc = 0 ;
                    }
               if (nVscrollInc = max (-nVscrollPos,
                         min (nVscrollInc, nVscrollMax - nVscrollPos)))
                    {
                    nVscrollPos += nVscrollInc ;
                    ScrollWindow (hwnd, 0, -cyChar * nVscrollInc, NULL,
NULL) ;
                    SetScrollPos (hwnd, SB_VERT, nVscrollPos, TRUE) ;
                    UpdateWindow (hwnd) ;
                    }
               return 0 ;

          case WM_HSCROLL :
               switch (wParam)
                    {
                    case SB_LINEUP :
                         nHscrollInc = -1 ;
                         break ;

                    case SB_LINEDOWN :
                         nHscrollInc = 1 ;
                         break ;

                    case SB_PAGEUP :
                         nHscrollInc = -8 ;
                         break ;

                    case SB_PAGEDOWN :
                         nHscrollInc = 8 ;
                         break ;

                    case SB_THUMBPOSITION :
                         nHscrollInc = LOWORD (lParam) - nHscrollPos ;
                         break ;

                    default :
                         nHscrollInc = 0 ;
                    }
               if (nHscrollInc = max (-nHscrollPos,
                         min (nHscrollInc, nHscrollMax - nHscrollPos)))
                    {
                    nHscrollPos += nHscrollInc ;
                    ScrollWindow (hwnd, -cxChar * nHscrollInc, 0, NULL,
NULL) ;
                    SetScrollPos (hwnd, SB_HORZ, nHscrollPos, TRUE) ;
                    }
               return 0 ;

          case WM_KEYDOWN :
               switch (wParam)
                    {
                    case VK_HOME :
                         SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L) ;
                         break ;
                    case VK_END :
                         SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L) ;
                         break ;

                    case VK_PRIOR :
                         SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0L) ;
                         break ;

                    case VK_NEXT :
                         SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L) ;
                         break ;

                    case VK_UP :
                         SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0L) ;
                         break ;

                    case VK_DOWN :
                         SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0L) ;
                         break ;

                    case VK_LEFT :
                         SendMessage (hwnd, WM_HSCROLL, SB_PAGEUP, 0L) ;
                         break ;

                    case VK_RIGHT :
                         SendMessage (hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L) ;
                         break ;
                    }
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               nPaintBeg = max (0, nVscrollPos + ps.rcPaint.top / cyChar -
1) ;
               nPaintEnd = min (NUMLINES,
                                nVscrollPos + ps.rcPaint.bottom / cyChar) ;

               for (i = nPaintBeg ; i < nPaintEnd ; i++)
                    {
                    x = cxChar * (1 - nHscrollPos) ;
                    y = cyChar * (1 - nVscrollPos + i) ;

                    TextOut (hdc, x, y,
                             sysmetrics[i].szLabel,
                    lstrlen (sysmetrics[i].szLabel)) ;

                    TextOut (hdc, x + 18 * cxCaps, y,
                             sysmetrics[i].szDesc,
                    lstrlen (sysmetrics[i].szDesc)) ;
                    SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

                    TextOut (hdc, x + 18 * cxCaps + 40 * cxChar, y,
                             szBuffer,
                             wsprintf (szBuffer, "%5d",
                    GetSystemMetrics (sysmetrics[i].nIndex))) ;

                    SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
                    }

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }

     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 SYSMETS.DEF

;------------------------------------
; SYSMETS.DEF module definition file
;------------------------------------

NAME           SYSMETS

DESCRIPTION    'System Metrics Display (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc



CHARACTER MESSAGES

Earlier I discussed the idea of translating keystroke messages into
character messages by taking into account shift-state information, and I
warned that shift-state information is not enough: You also need to know
about country-dependent keyboard configurations. For this reason, you should
not attempt to translate keystroke messages into character codes yourself.

Windows does it for you. You've seen this code before:

while (GetMessage (&msg, NULL, 0, 0))
     {
     TranslateMessage (&msg) ;
     DispatchMessage (&msg) ;
     }

This is a typical message loop that appears in WinMain. The GetMessage
function fills in the msg structure fields with the next message from the
queue. DispatchMessage calls the appropriate window procedure with this
message.

Between these two functions is TranslateMessage, which translates keystroke
messages into character messages. If the message is WM_KEYDOWN or
WM_SYSKEYDOWN, and if the keystroke in combination with the shift states
produces a character, then TranslateMessage places a character message in
the message queue. This character message will be the next message that
GetMessage retrieves from the queue after the keystroke message.

There are four character messages:

                       Characters  Dead Characters
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Nonsystem Characters:  WM_CHAR     WM_DEADCHAR
System Characters:     WM_SYSCHAR  WM_SYSDEADCHAR

The WM_CHAR and WM_DEADCHAR messages are derived from WM_KEYDOWN messages.
The WM_SYSCHAR and WM_SYSDEADCHAR messages are derived from WM_SYSKEYDOWN
messages. In most cases, your Windows program can ignore everything except
WM_CHAR messages. The lParam parameter passed to the window procedure with
the character code message is the same as the lParam parameter for the
keystroke message that generated the character code message. The wParam
parameter is the ASCII code for the character (yes, good old familiar
ASCII).

The character messages are delivered to your window procedure sandwiched
between keystroke messages. For instance, if Caps Lock is not toggled on and
you press and release the A key, the window procedure receives the following
three messages:

Message                    Key or Code
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
WM_KEYDOWN                 Virtual key A
WM_CHAR                    ASCII code a
WM_KEYUP                   Virtual key A

If you type an uppercase A by pressing the Shift key, pressing the A key,
releasing the A key, and then releasing the Shift key, the window procedure
receives five messages:

Message                  Key or Code
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
WM_KEYDOWN               Virtual key VK_SHIFT
WM_KEYDOWN               Virtual key A
WM_CHAR                  ASCII code A
WM_KEYUP                 Virtual key A
WM_KEYUP                 Virtual key VK_SHIFT

The Shift key by itself does not generate a character message.

If you hold down the A key so that the typematic action generates
keystrokes, you'll get a character message for each WM_KEYDOWN message:

Message                    Key or Code
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
WM_KEYDOWN                 Virtual key A
WM_CHAR                    ASCII code a
WM_KEYDOWN                 Virtual key A
WM_CHAR                    ASCII code a
WM_KEYDOWN                 Virtual key A
WM_CHAR                    ASCII code a
WM_KEYDOWN                 Virtual key A
WM_CHAR                    ASCII code a
WM_KEYUP                   Virtual key A

If some of the WM_KEYDOWN messages have a Repeat Count greater than 1, the
corresponding WM_CHAR messages will have the same Repeat Count.

The Ctrl key in combination with a letter key generates ASCII control codes
from 01H (Ctrl-A) through 1AH (Ctrl-Z). You can also use other keys to
generate these control codes. The following table shows the value of wParam
in a WM_CHAR message for keys that generate control codes:

Key         ASCII Code  Duplicated by
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Backspace   08H         Ctrl-H
Tab         09H         Ctrl-I
Ctrl-Enter  0Ah         Ctrl-J
Enter       0Dh         Ctrl-M
Esc         1BH         Ctrl-[

Windows programs sometimes use the Ctrl key in combination with letter keys
for menu accelerators, in which case the letter keys are not translated into
character messages.

WM_CHAR Messages

When your Windows program needs to process characters from the keyboard (for
instance, in a word-processing or communications program), it will process
WM_CHAR messages. You'll probably want some special processing for the
Backspace, Tab, and Enter keys (and perhaps the Linefeed key), but you'll
treat all other characters the same:

case WM_CHAR :

     switch (wParam)
          {
          case '\b' :    // backspace
[other program lines]
               break ;

          case '\t' :    // tab
[other program lines]
               break ;

          case '\n' :    // linefeed
[other program lines]
               break ;

          case '\r' :    // carriage return
[other program lines]
               break ;

          default :      // character code
[other program lines]
               break ;
          }
     return 0 ;

This program fragment is virtually identical to keyboard character
processing in regular MS-DOS programs.


Dead-Character Messages

Windows programs can usually ignore WM_DEADCHAR and WM_SYSDEADCHAR messages.
On some non-U.S. keyboards, certain keys are defined to add a diacritic to a
letter. These are called "dead keys" because they don't create characters by
themselves. For instance, when the German keyboard is installed, the key
that is in the same position as the +/= key on a U.S. keyboard is a dead key
for the acute accent (\a) when unshifted and the grave accent (\g) when
shifted.

When a user presses this dead key, your window procedure receives a
WM_DEADCHAR message with wParam equal to the ASCII code for the diacritic by
itself. When the user then presses a letter key (for instance, the A key),
the window procedure receives a WM_CHAR message where wParam is the ASCII
code for the letter a with the diacritic. Thus, your program does not have
to process the WM_DEADCHAR message, because the WM_CHAR message gives the
program all the information it needs. The Windows logic even has built-in
error handling: If the dead key is followed by a letter that can't take a
diacritic (such as the letter s), then the window procedure receives two
WM_CHAR messages in a row--the first with wParam equal to the ASCII code for
the diacritic by itself (the same wParam value delivered with the
WM_DEADCHAR message) and the second with wParam equal to the ASCII code for
the letter s.



LOOKING AT KEYBOARD MESSAGES

If you'd like to see how Windows sends keyboard messages to a program,
KEYLOOK, shown in Figure 3-3, will help. This program displays in its client
area all the information that Windows sends the window procedure for the
eight different keyboard messages.

 KEYLOOK.MAK

#-----------------------
# KEYLOOK.MAK make file
#-----------------------

keylook.exe : keylook.obj keylook.def
     link keylook, /align:16, NUL, /nod slibcew libw, keylook
     rc keylook.exe

keylook.obj : keylook.c
     cl -c -Gsw -Ow -W2 -Zp keylook.c

 KEYLOOK.C

/*-------------------------------------------------------
   KEYLOOK.C -- Displays Keyboard and Character Messages
                (c) Charles Petzold, 1990
  -------------------------------------------------------*/

#include 
#include 

long  FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;



RECT  rect ;
short cxChar, cyChar ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "KeyLook" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Keyboard Message Looker",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

void ShowKey (HWND hwnd, int iType, char *szMessage, WORD wParam, LONG
lParam)
     {
     static char *szFormat[2] = { "%-14s %3d    %c %6u %4d %3s %3s %4s %4s",
                                  "%-14s    %3d %c %6u %4d %3s %3s %4s %4s"
} ;
     char        szBuffer[80] ;
     HDC         hdc ;
     ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;
     hdc = GetDC (hwnd) ;

     SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

     TextOut (hdc, cxChar, rect.bottom - cyChar, szBuffer,
              wsprintf (szBuffer, szFormat [iType],
                        (LPSTR) szMessage, wParam,
                        (BYTE) (iType ? wParam : ' '),
                        LOWORD (lParam),
                        HIWORD (lParam) & 0xFF,
                        (LPSTR) (0x01000000 & lParam ? "Yes"  : "No"),
                        (LPSTR) (0x20000000 & lParam ? "Yes"  : "No"),
                        (LPSTR) (0x40000000 & lParam ? "Down" : "Up"),
                        (LPSTR) (0x80000000 & lParam ? "Up"   : "Down"))) ;

     ReleaseDC (hwnd, hdc) ;
     ValidateRect (hwnd, NULL) ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static char szTop[] =
                    "Message        Key Char Repeat Scan Ext ALT Prev Tran";
     static char szUnd[]=
                    "_______        ___ ____ ______ ____ ___ ___ ____ ____";
     HDC         hdc ;
     PAINTSTRUCT ps ;
     TEXTMETRIC  tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight ;

               ReleaseDC (hwnd, hdc) ;

               rect.top = 3 * cyChar / 2 ;
               return 0 ;

          case WM_SIZE :
               rect.right  = LOWORD (lParam) ;
               rect.bottom = HIWORD (lParam) ;
               UpdateWindow (hwnd) ;
               return 0 ;
          case WM_PAINT :
               InvalidateRect (hwnd, NULL, TRUE) ;
               hdc = BeginPaint (hwnd, &ps) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               SetBkMode (hdc, TRANSPARENT) ;
               TextOut (hdc, cxChar, cyChar / 2, szTop, (sizeof szTop) - 1)
;
               TextOut (hdc, cxChar, cyChar / 2, szUnd, (sizeof szUnd) - 1)
;
               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_KEYDOWN :
               ShowKey (hwnd, 0, "WM_KEYDOWN", wParam, lParam) ;
               return 0 ;

          case WM_KEYUP :
               ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam) ;
               return 0 ;

          case WM_CHAR :
               ShowKey (hwnd, 1, "WM_CHAR", wParam, lParam) ;
               return 0 ;

          case WM_DEADCHAR :
               ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam) ;
               return 0 ;

          case WM_SYSKEYDOWN :
               ShowKey (hwnd, 0, "WM_SYSKEYDOWN", wParam, lParam) ;
               break ;        // i.e., call DefWindowProc

          case WM_SYSKEYUP :
               ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam) ;
               break ;        // i.e., call DefWindowProc

          case WM_SYSCHAR :
               ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam) ;
               break ;        // i.e., call DefWindowProc

          case WM_SYSDEADCHAR :
               ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam) ;
               break ;        // i.e., call DefWindowProc

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 KEYLOOK.DEF

;------------------------------------
; KEYLOOK.DEF module definition file
;------------------------------------

NAME           KEYLOOK

DESCRIPTION    'Key Look Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

KEYLOOK uses the display like an old-fashioned teletype output device. When
KEYLOOK receives a keystroke message, it calls ScrollWindow to scroll the
contents of the entire client area of the window so that the contents move
up the height of one character. TextOut is used to display the line of new
information beginning one character height from the bottom. This is about as
simple as a teletype output can get. Figure 3-4 shows what the KEYLOOK
display looks like when you type the word "Windows." The first column shows
the keyboard message, the second shows the virtual key code for keystroke
messages, the

  (Figure 3-4. may be found in the printed book.)

third shows the character code (and the character itself) for character
messages, and the other six columns show the states of the six fields in the
lParam message parameter.

Most of KEYLOOK.C uses features of Windows that have already been covered in
the various SYSMETS programs, but a few new functions are used here.

The column formatting of KEYLOOK would be difficult with the default
proportional font. The code to display each line would need to be broken
into nine sections to get everything lined up. For something like this, a
much easier approach is to simply switch to a fixed-pitch font. This
requires two functions in a single statement:

SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

KEYLOOK calls these two functions whenever it obtains a device context. This
occurs in three places: the ShowKey function, while processing the WM_CREATE
message in WndProc, and while processing the WM_PAINT message. The
GetStockObject function obtains a handle to a "stock" graphics object, which
is a predefined graphics object that Windows makes available to programs. In
this case, GetStockObject obtains a handle to a font known as
SYSTEM_FIXED_FONT, which is the fixed-pitch font that was used in versions
of Windows prior to Windows 3. The SelectObject call places that object into
the device context. Following this call, all text that is displayed will use
the fixed-pitch font. It is possible to switch back to the default
proportional font by calling:

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

I'll discuss these functions in more depth in Chapter 12.

The ShowKey function calls ScrollWindow to scroll the previous lines of
keystrokes up before displaying a new line. Normally this would cause part
of the window to become invalid and hence generate a WM_PAINT message. The
ShowKey function concludes with a call to ValidateRect to prevent this.

Notice the use of the Windows wsprintf function in the ShowKey function. The
character strings must be explicitly cast to far pointers using the LPSTR
data type (defined in WINDOWS.H as a far pointer to a character string). The
wsprintf function is one of the very few functions in Windows that
explicitly requires casting of its parameters.

KEYLOOK does not save the keystrokes it receives, so on receipt of a
WM_PAINT message it cannot re-create the window. For this reason, KEYLOOK
simply displays the header at the top of the client area during the WM_PAINT
message. Before calling BeginPaint during the WM_PAINT message, KEYLOOK
invalidates the entire window. This allows the whole window to be erased
rather than just the invalid rectangle.

(That KEYLOOK does not save the keystrokes and hence cannot redraw the
window during a WM_PAINT message is certainly a flaw. The TYPE program shown
later in this chapter corrects this flaw.)

KEYLOOK draws a header at the top of the client area identifying the nine
columns. Although it's possible to create an underlined font, I took a
slightly different approach  here. I defined two character string variables
named szTop (which has the text) and szUnd (which has the underlining) and
displayed both of them at the same position at the top of the window during
the WM_PAINT message. Normally, Windows displays text in an  "opaque" mode,
meaning that Windows erases the character background area while displaying a
character. This would cause the second character string (szUnd) to erase the
first (szTop). To prevent this, switch the device context into the
"transparent" mode:

SetBkMode (hdc, TRANSPARENT) ;


THE CARET (NOT THE CURSOR)

When you type text into a program, generally a little underline or box shows
you where the next character you type will appear on the screen. You may
know this as a "cursor," but you'll have to get out of that habit when
programming for Windows. In Windows, it's called the "caret." The word
"cursor" is used for the bitmap image that represents the mouse position.

The Caret Functions

There are five essential caret functions:

  þ   CreateCaret creates a caret associated with a window.

  þ   SetCaretPos sets the position of the caret on the window.

  þ   ShowCaret shows the caret.

  þ   HideCaret hides the caret.

  þ   DestroyCaret destroys the caret.

There are also functions to get the caret position (GetCaretPos) and to get
and set the caret blink time (GetCaretBlinkTime and SetCaretBlinkTime).

The caret is customarily a horizontal line, or a box that is the size of a
character, or a vertical line. The vertical line is recommended when you use
a proportional font such as the Windows default system font. Because the
characters in a proportional font are not a fixed size, the horizontal line
and box can't be set to the size of a character.

You cannot simply create a caret during the WM_CREATE message and destroy it
during the WM_DESTROY message. The caret is what is known as a "systemwide
resource." What this means is that there is only one caret in the system. In
effect, a program "borrows" the caret from the system when it needs to
display a caret in its window.

Does this sound bizarrely restrictive? It's really not. Think about it: The
display of a caret in a window makes sense only when the window has the
input focus. This indicates to the user that he or she may enter text in the
program. Only one window has the input focus at any time so only one caret
is needed in the whole system.

A program can determine if it has the input focus by processing the
WM_SETFOCUS and WM_KILLFOCUS messages. A window procedure receives a
WM_SETFOCUS message when it receives the input focus, and a WM_KILLFOCUS
message when it loses the input focus. These messages occur in pairs: A
window procedure will always receive a WM_SETFOCUS message before it
receives a WM_KILLFOCUS message, and it always receives an equal number of
WM_SETFOCUS and WM_KILLFOCUS messages over the course of the window's
lifetime.

The main rule for using the caret is simple: A window procedure calls
CreateCaret during the WM_SETFOCUS message and DestroyCaret during the
WM_KILLFOCUS message.

There are a few other rules: The caret is created hidden. After calling
CreateCaret, the window procedure must call ShowCaret for the caret to be
visible. In addition, the window procedure must hide the caret by calling
HideCaret whenever it draws something on its window during a message other
than WM_PAINT. After it finishes drawing on the window, it calls ShowCaret
to display the caret again. The effect of HideCaret is additive: If you call
HideCaret several times without calling ShowCaret, you must call ShowCaret
the same number of times before the caret becomes visible again.


The TYPE Program

The TYPE program shown in Figure 3-5 (beginning on the following page)
brings together much of what we've learned in this chapter. You can think of
TYPE as an extremely rudimentary text editor. You can type in the window,
move the cursor (I mean caret) around with the cursor movement (or are they
caret movement?) keys, and erase the contents of the window by pressing
Escape. The contents of the window are also erased when you resize the
window. There's no scrolling, no search and replace, no way to save files,
and no spell checker, but it's a start.

To make things easy for myself, TYPE uses SYSTEM_FIXED_FONT. Writing a text
editor for a proportional font is, as you might imagine, much more
difficult. The program obtains a device context in several places: during
the WM_CREATE message, the WM_KEYDOWN message, the WM_CHAR message, and the
WM_PAINT message. Each time, calls to GetStockObject and SelectObject select
the fixed-pitch font.

During the WM_SIZE message, TYPE calculates the character width and height
of the window and saves these values in the variables cxBuffer and cyBuffer.
It then uses malloc to allocate a buffer to hold all the characters that can
be typed in the window. The xCaret and yCaret variables store the character
position of the caret.

During the WM_SETFOCUS message, TYPE calls CreateCursor to create a cursor
that is the width and height of a character, SetCaretPos to set the caret
position, and ShowCaret to make the caret visible. During the WM_KILLFOCUS
message, TYPE calls HideCaret and DestroyCaret.

 TYPE.MAK

#--------------------
# TYPE.MAK make file
#--------------------

type.exe : type.obj type.def
     link type, /align:16, NUL, /nod slibcew libw, type
     rc type.exe

type.obj : type.c
     cl -c -Gsw -Ow -W2 -Zp type.c

 TYPE.C

/*-------------------------------------
   TYPE.C -- Typing Program
             (c) Charles Petzold, 1990
  -------------------------------------*/

#include 
#include 

#define BUFFER(x, y) *(pBuffer + y * cxBuffer + x)

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Type" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;



          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Typing Program",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static char *pBuffer = NULL ;
     static int  cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer,
                 xCaret, yCaret ;
     HDC         hdc ;
     int         x, y, i ;
     PAINTSTRUCT ps ;
     TEXTMETRIC  tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight ;

               ReleaseDC (hwnd, hdc) ;
               return 0 ;

          case WM_SIZE :
                                   // obtain window size in pixels
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;

                                   // calculate window size in characters

               cxBuffer = max (1, cxClient / cxChar) ;
               cyBuffer = max (1, cyClient / cyChar) ;

                                   // allocate memory for buffer and clear
it

               if (pBuffer != NULL)
                    free (pBuffer) ;

               if ((LONG) cxBuffer * cyBuffer > 65535L ||
                         (pBuffer = malloc (cxBuffer * cyBuffer)) == NULL)

                    MessageBox (hwnd, "Window too large.  Cannot "
                                      "allocate enough memory.", "Type",
                                MB_ICONEXCLAMATION | MB_OK) ;

               else
                    for (y = 0 ; y < cyBuffer ; y++)
                         for (x = 0 ; x < cxBuffer ; x++)
                              BUFFER(x, y) = ' ' ;

                                   // set caret to upper left corner
               xCaret = 0 ;
               yCaret = 0 ;

               if (hwnd == GetFocus ())
                    SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               return 0 ;

          case WM_SETFOCUS :
                                   // create and show the caret

               CreateCaret (hwnd, NULL, cxChar, cyChar) ;
               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
               ShowCaret (hwnd) ;
               return 0 ;

          case WM_KILLFOCUS :
                                   // hide and destroy the caret
               HideCaret (hwnd) ;
               DestroyCaret () ;
               return 0 ;
          case WM_KEYDOWN :
               switch (wParam)
                    {
                    case VK_HOME :
                         xCaret = 0 ;
                         break ;

                    case VK_END :
                         xCaret = cxBuffer - 1 ;
                         break ;

                    case VK_PRIOR :
                         yCaret = 0 ;
                         break ;

                    case VK_NEXT :
                         yCaret = cyBuffer - 1 ;
                         break ;

                    case VK_LEFT :
                         xCaret = max (xCaret - 1, 0) ;
                         break ;

                    case VK_RIGHT :
                         xCaret = min (xCaret + 1, cxBuffer - 1) ;
                         break ;

                    case VK_UP :
                         yCaret = max (yCaret - 1, 0) ;
                         break ;

                    case VK_DOWN :
                         yCaret = min (yCaret + 1, cyBuffer - 1) ;
                         break ;

                    case VK_DELETE :
                         for (x = xCaret ; x < cxBuffer - 1 ; x++)
                              BUFFER (x, yCaret) = BUFFER (x + 1, yCaret) ;

                         BUFFER (cxBuffer - 1, yCaret) = ' ' ;

                         HideCaret (hwnd) ;
                         hdc = GetDC (hwnd) ;

                         SelectObject (hdc,
                              GetStockObject (SYSTEM_FIXED_FONT)) ;

                         TextOut (hdc, xCaret * cxChar, yCaret * cyChar,
                                  & BUFFER (xCaret, yCaret),
                                  cxBuffer - xCaret) ;
                         ShowCaret (hwnd) ;
                         ReleaseDC (hwnd, hdc) ;
                         break ;
                    }

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
               return 0 ;

          case WM_CHAR :
               for (i = 0 ; i < LOWORD (lParam) ; i++)
                    {
                    switch (wParam)
                         {
                         case '\b' :                    // backspace
                              if (xCaret > 0)
                                   {
                                   xCaret-- ;
                                   SendMessage (hwnd, WM_KEYDOWN,
                                                VK_DELETE, 1L) ;
                                   }
                              break ;

                         case '\t' :                    // tab
                              do
                                   {
                                   SendMessage (hwnd, WM_CHAR, ' ', 1L) ;
                                   }
                              while (xCaret % 8 != 0) ;
                              break ;

                         case '\n' :                    // linefeed
                              if (++yCaret == cyBuffer)
                                   yCaret = 0 ;
                              break ;

                         case '\r' :                    // carriage return
                              xCaret = 0 ;

                              if (++yCaret == cyBuffer)
                                   yCaret = 0 ;
                              break ;

                         case '\x1B' :                  // escape
                              for (y = 0 ; y < cyBuffer ; y++)
                                   for (x = 0 ; x < cxBuffer ; x++)
                                        BUFFER (x, y) = ' ' ;

                              xCaret = 0 ;
                              yCaret = 0 ;
                              InvalidateRect (hwnd, NULL, FALSE) ;
                              break ;

                         default :                      // character codes
                              BUFFER (xCaret, yCaret) = (char) wParam ;

                              HideCaret (hwnd) ;
                              hdc = GetDC (hwnd) ;

                              SelectObject (hdc,
                                   GetStockObject (SYSTEM_FIXED_FONT)) ;

                              TextOut (hdc, xCaret * cxChar, yCaret *
cyChar,
                                       & BUFFER (xCaret, yCaret), 1) ;

                              ShowCaret (hwnd) ;
                              ReleaseDC (hwnd, hdc) ;

                              if (++xCaret == cxBuffer)
                                   {
                                   xCaret = 0 ;

                                   if (++yCaret == cyBuffer)
                                        yCaret = 0 ;
                                   }
                              break ;
                         }
                    }

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               for (y = 0 ; y < cyBuffer ; y++)
                    TextOut (hdc, 0, y * cyChar, & BUFFER(0, y), cxBuffer) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 TYPE.DEF

;---------------------------------
; TYPE.DEF module definition file
;---------------------------------

NAME           TYPE

DESCRIPTION    'Typing Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The processing of the WM_KEYDOWN and WM_CHAR messages is more extensive. The
WM_KEYDOWN processing mostly involves the cursor movement keys. Home and End
send the caret to the beginning and end of a line respectively, and Page Up
and Page Down send the caret to the top and bottom of the window. The arrow
keys work as you would expect. For the Delete key, TYPE must move everything
left in the buffer from the next caret position to the end of the line and
then display a blank at the end of the line.

The WM_CHAR processing handles the Backspace, Tab, Linefeed (Ctrl-Enter),
Enter, Escape, and character keys. Notice I've used Repeat Count in lParam
when processing the WM_CHAR message (under the assumption that every
character the user types is important) but not during the WM_KEYDOWN message
(to prevent inadvertent overscrolling). The Backspace and Tab processing is
simplified somewhat by the use of the SendMessage function. Backspace is
emulated by the Delete logic, and Tab is emulated by a series of spaces.

As I mentioned earlier, you should hide the cursor when drawing on the
window during messages other then WM_PAINT. The program does this when
processing the WM_KEYDOWN message for the Delete key and the WM_CHAR message
for character keys. In both these cases, TYPE alters the contents of the
buffer and then draws the new character or characters on the window.

I use TYPE when working on speeches, as shown in Figure 3-6.

  (Figure 3-6. may be found in the printed book.)



THE WINDOWS CHARACTER SETS

I mentioned earlier that letter keys preceded by dead-character keys
generate WM_CHAR messages where wParam is the ASCII code for a character
with a diacritic. This may be a little puzzling because the ASCII character
set doesn't include any codes for characters with diacritics. What exactly
is the value of wParam in this case? The answer to this question requires
that we tackle the subject of character sets, a topic that may at first seem
more appropriate for a later discussion about character fonts. However, it
is also of vital importance in keyboard handling.

The standard 7-bit ASCII character set defines codes from 0 through 31 (1FH)
and 127 (7FH) as control characters, and it defines codes from 32 (20H)
through 126 (7EH) as displayable characters. None of these characters have
diacritics. Because personal computers use 8-bit bytes, computer
manufacturers often define character sets that use 256 codes rather than the
128 ASCII codes. The additional codes may be assigned characters with
diacritics. The resultant "extended character set" then includes the ASCII
character set and up to 128 other characters.

If Windows supported such an extended character set, displaying characters
with diacritics would be easy. But Windows doesn't support a simple extended
character set. Windows supports two extended character sets. Unfortunately,
the presence of these two character sets doesn't make things twice as easy.

The OEM Character Set

First, let's go back to the hardware that Windows runs on--the IBM PC and
compatibles. In the early 1980s the developers of the IBM PC decided to
extend the ASCII character set as shown in Figure 3-7. The codes from 20H
through 7EH are displayable characters from the ASCII character set. The
rest are nonstandard--or at least were at the time.

This character set cannot be ignored. It is encoded in millions of ROM chips
in IXC video adapters, printers, and system board BIOS's. It has been
duplicated in the hardware of numerous manufacturers of IXC-compatible
computers and peripherals. This character set is part of what is meant by
the phrase "the IXC standard." Many programs written for the IBM PC require
this extended character set because they use the block-drawing and
line-drawing characters (codes B0H through DFH) in their screen output.

  (Figure 3-7. may be found in the printed book.)

The only problem is this: The IXC extended character set is inappropriate
for Windows. First, the block-drawing and line-drawing characters commonly
used by PC programs in text-mode applications are not needed in Windows
because Windows does real graphics. If you want to draw a horizontal line in
Windows, it's easier to draw a line than to display a string of C4H
characters. Second, the Greek alphabet and mathematical symbols are less
important in Windows than are the accented letters used in most European
languages. A program that needs to display mathematical symbols can best
draw them using graphics functions.

In short, Windows supports the IXC character set, but it is relegated to
secondary importance, mostly for old applications that run in a window.
Windows applications do not normally use the IXC character set. In Windows
documentation, the IXC character set is referred to as the "OEM character
set." The OEM character set is more precisely defined as the character set
that is native to the machine currently running Windows.

International Support under DOS

There are a number of variants on the IBM PC character set, called "code
pages." The variant used in the United States and in most European countries
is called Code Page 437. Systems sold in Norway, Denmark, Portugal, and a
few other European countries use different, special, code pages, which
contain more of the special characters required by the languages of those
countries. Recently, a number of these countries began to use Code Page 850,
which contains fewer graphics symbols and more accented letters and other
special characters.

Windows 3.0 supports code pages by installing OEM fonts (used for running
DOS applications in windows and in the clipboard viewer), which correspond
to the system's code page, and by installing appropriate translation tables
for the AnsiToOem and OemToAnsi functions (discussed later). If the system
is running DOS version 3.3 or later, the Windows Setup program will use the
current DOS code page. For earlier versions of DOS, Setup will select a code
page based on the localized (national) version of Windows.



The ANSI Character Set

The extended character set that Windows and Windows programs use for most
purposes is called the "ANSI character set." When your program receives a
WM_CHAR message, the wParam parameter is the ANSI character code. The ANSI
character set is shown in Figure 3-8. As you can see, the codes from 20H
through 7EH represent the same characters that  appear in the OEM character
set and the ASCII character set. The characters displayed as solid blocks
are undefined characters. They may appear differently on other output
devices (such as a printer).

  (Figure 3-8. may be found in the printed book.)


OEM, ANSI, and Fonts

Windows has different fonts for displaying the ANSI and OEM character sets.
When you first obtain a handle to a device context, one of the attributes in
the device context is a font.

By default this is the SYSTEM_FONT or "system font," which uses the ANSI
character set. If you want to display characters from the OEM character set,
you can select the OEM_FIXED_FONT (also called the "terminal font") in the
device context by using the following code:

SelectObject (hdc, GetStockObject (OEM_FIXED_FONT)) ;

This is the only font in Windows guaranteed to support the OEM character
set.



INTERNATIONALIZATION CONCERNS

Here's why we have to talk about fonts in the middle of the keyboard
chapter. We've established that when a Windows user on a non-U.S. keyboard
types a character with a diacritic, the wParam parameter of the WM_CHAR
message is the code for that character in the ANSI character set.

So, if you need to echo that character to the display, you had better be
using a font with the ANSI character set (such as the SYSTEM_FONT or
SYSTEM_FIXED_FONT). If you instead use the OEM_FIXED_FONT, the character you
write to the display will be incorrect and will surprise the user. A few
other simple rules will allow the keyboard logic in your Windows programs to
survive intact when you convert your programs for a Euro- pean market.

Working with the Character Set

When you get a WM_CHAR message, keep in mind that wParam may legitimately
have values above 128. Don't assume that anything above 127 is an invalid
character.

You may want to convert a character to uppercase. Don't use your own
algorithm:

if (ch >= 'a' && ch <= 'z')
     ch -= 32 ;               // WRONG !!!

That's a poor practice even when writing non-Windows C. But don't use the
standard C function either:

ch = toupper (ch) ;           // WRONG !!!

Both these functions work only for the lower half of the ANSI character set.
They will not convert a C0H to an E0H.

Instead, you should use the Windows functions AnsiUpper and AnsiLower. If
str is a zero-terminated character string, you can convert it to uppercase
using AnsiUpper:

AnsiUpper (pString) ;

or using the AnsiUpperBuff function for character strings that are not
zero-terminated:

AnsiUpperBuff (pString, nLength) ;

You can also use AnsiUpper to convert a single character, but some casting
is required because the high-order word of the parameter must be zero:

ch = AnsiUpper ((LPSTR) (LONG) (BYTE) ch) ;

If ch is defined as an unsigned character, the initial BYTE cast is not
required. Windows also includes AnsiLower and AnsiLowerBuff functions for
converting to lowercase.

If you are really serious about writing Windows programs that can be easily
converted to foreign languages, you should also investigate the AnsiNext and
AnsiPrev functions. These functions facilitate handling of multibyte
character sets. The Japanese character set requires more than 256
characters, some of which use 2 bytes. If you use normal C pointer
arithmetic to scan a string (perhaps searching for a backslash character in
a directory path string), you may think you've found the character when
you've really found the second byte of a 2-byte character code. AnsiNext and
AnsiPrev take a far pointer to a character string and return a far pointer
that has been correctly incremented or decremented past 2-byte character
codes.


Talking with MS-DOS

If Windows were the only operating environment running on a machine, then
you could forget about the OEM character set and use only the ANSI character
set. However, users can create files in the MS-DOS environment and use them
in Windows; they can also create files in Windows and use them when back in
MS-DOS. Unfortunately, MS-DOS uses the OEM character set.

Here's an example of the communications problems that can occur. Suppose
that a German-speaking PC user creates a file named \UUBUNGEN.TXT ("practice
exercises") in an MS-DOS program such as EDLIN. On the IBM PC, the \UU is
part of the IXC (that is, OEM) character set and has a code of 154 or 9AH.
(When using MS-DOS with a U.S. keyboard on an IBM PC, you can also create
this letter by typing Alt-154 using the numeric keypad.) MS-DOS uses that
character code in the directory entry of the file.

If a Windows program uses MS-DOS function calls to obtain a directory of
files and then writes them directly to the display using an ANSI character
set font, the first letter of \UUBUNGEN will show up as a solid block,
because the code 154 is one of the undefined characters in the ANSI
character set. The Windows program needs to convert the IXC extended
character set code of 154 (9AH) to an ANSI character set code of 220 (or
DCH), which is the letter \UU in the ANSI character set. That's what the
Windows function  OemToAnsi does for you. It requires two far pointers to
strings. The OEM characters in the first string are converted to ANSI
characters and stored in the second string:

OemToAnsi (lpszOemStr, lpszAnsiStr) ;

Now let's take the opposite example. The German-speaking user wants your
Windows program to create a file named \UUBUNGEN.TXT. The filename entered
by the user has a 220 (DCH) as the first character. If you use an MS-DOS
function call to open the file, MS-DOS uses that character in the filename.
When the user later looks at the file under MS-DOS, the first character
shows up as a block. Before you use the MS-DOS function calls, you must
convert the filename to the OEM character set:

AnsiToOem (lpszAnsiStr, lpszOemStr) ;

This converts a 220 (DCH) to a 154 (9AH). Windows also includes two
functions named AnsiToOemBuff and OemToAnsiBuff that do not require a
zero-terminated string.

Windows has an OpenFile call that will convert this for you. If you use
OpenFile, don't do your own AnsiToOem conversion. If you use MS-DOS function
calls to obtain lists of filenames (as the Windows File Manager program
does), then these filenames should be passed through OemToAnsi before being
displayed.

Converting the contents of files is another problem that arises when files
are used in both Windows and MS-DOS. If your Windows program uses files that
you are certain have been created in an MS-DOS program, then you may need to
pass the text contents of the file through the OemToAnsi function. (For
instance, Windows WRITE does this when converting Microsoft Word files to
WRITE format.) Similarly, if your Windows program is preparing a file for
use in an MS-DOS program, you may want to use AnsiToOem to convert the text.

The OemToAnsi and AnsiToOem functions are located in the keyboard driver.
They incorporate very simple lookup tables. The OemToAnsi routine converts
an OEM code from 80H through FFH to a character code in the ANSI set that
most closely resembles the OEM character. In some cases, this conversion is
only grossly approximate. For instance, most of the line-drawing characters
in the IXC character set are translated as plus signs, dashes, and vertical
lines. Most of the OEM codes from 00H through 1FH are not translated to ANSI
codes.

The AnsiToOem routine converts ANSI codes from A0H through FFH into codes in
the OEM set. The accented characters in the ANSI character set that do not
appear in the OEM character set are translated into regular ASCII codes for
the characters without the diacritics.


Using the Numeric Keypad

As you probably know, the IBM PC keyboard and BIOS let you enter codes for
the IXC extended character set by pressing and holding down the Alt key,
typing on the numeric keypad the three-digit decimal code representing the
OEM character, and releasing the Alt key. This facility is duplicated in
Windows in two ways:

First, when you type Alt-[OEM code] on the numeric keypad, Windows gives to
you the ANSI character code in the wParam parameter of the WM_CHAR message
that most closely approximates the OEM character represented by the OEM
code. That is, Windows passes the code through the OemToAnsi function before
generating the WM_CHAR message. This facility is for the user's convenience:
If you do not have a foreign-language keyboard and you are accustomed to
typing a \UU by typing Alt-154, you can do the same thing in a Windows
program. You don't need to relearn the ANSI character codes.

Second, if you want to generate ANSI extended character codes from the U.S.
keyboard, type Alt-0[OEM code] on the numeric keypad. The wParam parameter
of the WM_CHAR message is that OEM code. Thus, Alt-0220 is also a \UU. You
can try this out in the TYPE program.