VC++ 6.0 ebook chapter index
homepage aranna.altervista.org
Free counters!

Keyboard Messages and Character Sets

The remaining sample programs in this chapter have flaws. They will not always run correctly under all versions of Windows. Their flaws are not something I deliberately introduced into the code; indeed, you might never notice them. These problems—I hesitate to call them "bugs"—reveal themselves only when switching among certain different keyboard languages and layouts, and when running the programs under Far Eastern versions of Windows that use multibyte character sets.

However, the programs will work much better when compiled for Unicode and run under Windows NT. This is the promise I made in Chapter 2, and it demonstrates why Unicode is so important in simplifying the work involved in internationalization.

The KEYVIEW1 Program

The first step in understanding keyboard internationalization issues is to examine the contents of the keyboard and character messages that Windows delivers to your window procedure. The KEYVIEW1 program shown in Figure 6-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.

Figure 6-3. The KEYVIEW1 program.

KEYVIEW1.C

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

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("KeyView1") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     
     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 ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Keyboard Message Viewer #1"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar ;
     static int   cLinesMax, cLines ;
     static PMSG  pmsg ;
     static RECT  rectScroll ;
     static TCHAR szTop[] = TEXT ("Message        Key       Char     ")
                            TEXT ("Repeat Scan Ext ALT Prev Tran") ;
     static TCHAR szUnd[] = TEXT ("_______        ___       ____     ")
                            TEXT ("______ ____ ___ ___ ____ ____") ;

     static TCHAR * szFormat[2] = { 
          
               TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),
               TEXT ("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;
     static TCHAR * szYes  = TEXT ("Yes") ;
     static TCHAR * szNo   = TEXT ("No") ;
     static TCHAR * szDown = TEXT ("Down") ;
     static TCHAR * szUp   = TEXT ("Up") ;

     static TCHAR * szMessage [] = { 
                         TEXT ("WM_KEYDOWN"),    TEXT ("WM_KEYUP"), 
                         TEXT ("WM_CHAR"),       TEXT ("WM_DEADCHAR"), 
                         TEXT ("WM_SYSKEYDOWN"), TEXT ("WM_SYSKEYUP"), 
                         TEXT ("WM_SYSCHAR"),    TEXT ("WM_SYSDEADCHAR") } ;
     HDC          hdc ;
     int          i, iType ;
     PAINTSTRUCT  ps ;
     TCHAR        szBuffer[128], szKeyName [32] ;
     TEXTMETRIC   tm ;
     
     switch (message)
     {
     case WM_CREATE:
     case WM_DISPLAYCHANGE:
     
               // Get maximum size of client area

          cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;
          cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;

              // Get character size for fixed-pitch font

          hdc = GetDC (hwnd) ;

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

          ReleaseDC (hwnd, hdc) ;

               // Allocate memory for display lines

          if (pmsg)
               free (pmsg) ;

          cLinesMax = cyClientMax / cyChar ;
          pmsg = malloc (cLinesMax * sizeof (MSG)) ;
          cLines = 0 ;
                                   // fall through
     case WM_SIZE:
          if (message == WM_SIZE)
          {
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
          }
               // Calculate scrolling rectangle

          rectScroll.left   = 0 ;
          rectScroll.right  = cxClient ;
          rectScroll.top    = cyChar ;
          rectScroll.bottom = cyChar * (cyClient / cyChar) ;

          InvalidateRect (hwnd, NULL, TRUE) ;
          return 0 ;
          
     case WM_KEYDOWN:
     case WM_KEYUP:
     case WM_CHAR:
     case WM_DEADCHAR:
     case WM_SYSKEYDOWN:
     case WM_SYSKEYUP:
     case WM_SYSCHAR:
     case WM_SYSDEADCHAR: 

               // Rearrange storage array

          for (i = cLinesMax - 1 ; i > 0 ; i--)
          {
               pmsg[i] = pmsg[i - 1] ;
          }
               // Store new message

          pmsg[0].hwnd = hwnd ;
          pmsg[0].message = message ;
          pmsg[0].wParam = wParam ;
          pmsg[0].lParam = lParam ;

          cLines = min (cLines + 1, cLinesMax) ;

               // Scroll up the display

          ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;

          break ;        // i.e., call DefWindowProc so Sys messages work

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

          SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
          SetBkMode (hdc, TRANSPARENT) ;
          TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;
          TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;

          for (i = 0 ; i < min (cLines, cyClient / cyChar - 1) ; i++)
          {
               iType = pmsg[i].message == WM_CHAR ||
                       pmsg[i].message == WM_SYSCHAR ||
                       pmsg[i].message == WM_DEADCHAR ||
                       pmsg[i].message == WM_SYSDEADCHAR ;

               GetKeyNameText (pmsg[i].lParam, szKeyName, 
                               sizeof (szKeyName) / sizeof (TCHAR)) ;

               TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,
                        wsprintf (szBuffer, szFormat [iType],
                             szMessage [pmsg[i].message - WM_KEYFIRST],
                             pmsg[i].wParam,
                             (PTSTR) (iType ? TEXT (" ") : szKeyName),
                             (TCHAR) (iType ? pmsg[i].wParam : ` `),
                             LOWORD (pmsg[i].lParam),
                             HIWORD (pmsg[i].lParam) & 0xFF,
                             0x01000000 & pmsg[i].lParam ? szYes  : szNo,
                             0x20000000 & pmsg[i].lParam ? szYes  : szNo,
                             0x40000000 & pmsg[i].lParam ? szDown : szUp,
                             0x80000000 & pmsg[i].lParam ? szUp   : szDown)) ;
          }
          EndPaint (hwnd, &ps) ;
          return 0 ;

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

KEYVIEW1 displays the contents of each keystroke and character message that it receives in its window procedure. It saves the messages in an array of MSG structures. The size of the array is based on the size of the maximized window size and the fixed-pitch system font. If the user resizes the video display while the program is running (in which case KEYVIEW1 gets a WM_DISPLAYCHANGE message), the array is reallocated. KEYVIEW1 uses the standard C malloc function to allocate memory for this array.

Figure 6-4 shows the KEYVIEW1 display after the word "Windows" has been typed. The first column shows the keyboard message. The second column shows the virtual key code for keystroke messages followed by the name of the key. This is obtained by using the GetKeyNameText function. The third column (labeled "Char") shows the hexadecimal character code for character messages followed by the character itself. The remaining six columns display the status of the six fields in the lParam message parameter.

click here to view full size

Figure 6-4. The KEYVIEW1 display.

To ease the columnar display of this information, KEYVIEW1 uses a fixed-pitch font. As discussed in the last chapter, this requires calls to GetStockObject and SelectObject:

SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

KEYVIEW1 draws a header at the top of the client area identifying the nine columns. The text in this column is underlined. Although it's possible to create an underlined font, I took a 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) ;

This method of underlining is possible only when using a fixed-pitch font. Otherwise, the underline character wouldn't necessarily be the same width as the character the underline is to appear under.

The Foreign-Language Keyboard Problem

If you're running the American English version of Windows, you can install different keyboard layouts and pretend that you're typing in a foreign language. You install foreign language keyboard layouts in the Keyboard applet in the Control Panel. Select the Language tab, and click Add. To see how dead keys work, you might want to install the German keyboard. I'll also be discussing the Russian and Greek keyboard layouts, so you might want to install those as well. If the Russian and Greek keyboard layouts are not available in the list that the Keyboard applet displays, you might need to install multilanguage support. Select the Add/Remove Programs applet from the Control Panel, and choose the Windows Setup tab. Make sure the Multilanguage Support box is checked. In any case, you'll need to have your original Windows CD-ROM handy for these changes.

After you install other keyboard layouts, you'll see a blue box with a two-letter code in the tray at the right side of the task bar. It'll be "EN" if the default is English. When you click on this icon, you get a list of all the installed keyboard layouts. You can change the keyboard for the currently active program by clicking on the one you want. This change affects only the currently active program.

Now we're ready to experiment. Compile the KEYVIEW1 program without the UNICODE identifier defined. (On this book's companion disc, the non-Unicode version of KEYVIEW1 is located in the RELEASE subdirectory.) Run the program under the American English version of Windows, and type the letters "abcde." The WM_CHAR messages are exactly what you expect: the ASCII character codes 0x61, 0x62, 0x63, 0x64, and 0x65 and the characters a, b, c, d, and e.

Now, while still running KEYVIEW1, select the German keyboard layout. Press the = key and then a vowel (a, e, i, o, or u). The = key generates a WM_DEADCHAR message, and the vowel generates a WM_CHAR message with (respectively) the character codes 0xE1, 0xE9, 0xED, 0xF3, 0xFA, and the characters á, é, í, ó, and ú. This is how dead keys work.

Now select the Greek keyboard layout. Type "abcde" and what do you get? You get WM_CHAR messages with the character codes 0xE1, 0xE2, 0xF8, 0xE4, 0xE5, and the characters á, â, ø, ä, and å. Something doesn't seem to be right here. Shouldn't you be getting letters in the Greek alphabet?

Now switch to the Russian keyboard and again type "abcde." Now you get WM_CHAR messages with the character codes 0xF4, 0xE8, 0xF1, 0xE2, and 0xF3, and the characters ô, è, ñ, â, and ó. Again, something is wrong. You should be getting letters in the Cyrillic alphabet.

The problem is this: you have switched the keyboard to generate different character codes, but you haven't informed GDI of this switch so that GDI can interpret these character codes by displaying the proper symbols.

If you're very brave, and you have a spare PC to play with, and if you have a Professional or Universal Subscription to Microsoft Developer Network (MSDN), you might want to install (for example) the Greek version of Windows. You can also install the same four keyboard layouts (English, Greek, German, and Russian). Now run KEYLOOK1. Switch to the English keyboard layout, and type "abcde". You get the ASCII character codes 0x61, 0x62, 0x63, 0x64, and 0x65 and the characters a, b, c, d, and e. (And you can breathe a sigh of relief that ASCII still works, even in Greece.)

Under this Greek version of Windows, switch to the Greek keyboard layout and type "abcde." You get WM_CHAR messages with the character codes 0xE1, 0xE2, 0xF8, 0xE4, and 0xE5. These are the same character codes you got under the English version of Windows with the Greek keyboard layout installed. But now the displayed characters are a, b, y, d, and e. These are indeed the lowercase Greek letters alpha, beta, psi, delta, and epsilon. (What happened to gamma? Well, if you were using the Greek version of Windows for real, you'd probably be using a keyboard with Greek letters on the keycaps. The key corresponding to the English c happens to be a psi. The gamma is generated by the key corresponding to the English g. You can see the complete Greek keyboard layout on page 587 of Nadine Kano's Developing International Software for Windows 95 and Windows NT.

Still running KEYVIEW1 under the Greek version of Windows, switch to the German keyboard layout. Type the = key followed by a, then e, then i, then o, and then u. You get WM_CHAR messages with the character codes 0xE1, 0xE9, 0xED, 0xF3, and 0xFA. These are the same character codes as under the English version of Windows with the German keyboard installed. However, the displayed characters are a, i, n, s, and i, not the correct á, é, í, ó, and ú.

Now switch to the Russian keyboard and type "abcde." You get the character codes 0xF4, 0xE8, 0xF1, 0xE2, and 0xF3, which are the same as under the English version of Windows with the Russian keyboard installed. However, the displayed characters are t, q, r, b, and s, not letters in the Cyrillic alphabet.

You can also install the Russian version of Windows. As you may have guessed by now, the English and Russian keyboard layouts will work, but not the German or Greek.

Now, if you're really, really brave, you can install the Japanese version of Windows and run KEYVIEW1. If you type at your American keyboard, you can enter English text and everything will seem to work fine. However, if you switch to the German, Greek, or Russian keyboard layouts and try any of the exercises described above, you'll see the characters displayed as dots. If you type capital letters—either accented German letters, Greek letters, or Russian letters—you'll see the characters rendered as katakana, which is the Japanese alphabet generally used to spell words from other languages. You may have fun typing katakana, but it's not German, Greek, or Russian.

The Far East versions of Windows include a utility called the Input Method Editor (IME) that appears as a floating toolbar. This utility lets you use the normal keyboard for entering ideographs, which are the complex characters used in Chinese, Japanese, and Korean. Basically, you type combinations of letters and the composed symbols appear in another floating window. You then press Enter and the resultant character codes are sent to the active window (that is, KEYVIEW1). KEYVIEW1 responds with almost total nonsense—the WM_CHAR messages have character codes above 128, but the characters are meaningless. (Nadine Kano's book has much more information on using the IME.)

So, we've seen a couple examples of KEYLOOK1 displaying incorrect characters—when running the English version of Windows with the Russian or Greek keyboard layouts installed, when running the Greek version of Windows with the Russian or German keyboard layouts installed, and when running the Russian version of Windows with the German, Russian, or Greek keyboards installed. We've also seen errors when entering characters from the Input Method Editor in the Japanese version of Windows.

Character Sets and Fonts

The problem with KEYLOOK1 is a font problem. The font that it's using to display characters on the screen is inconsistent with the character codes it's receiving from the keyboard. So, let's take a look at some fonts.

As I'll discuss in more detail in Chapter 17, Windows supports three types of fonts—bitmap fonts, vector fonts, and (beginning in Windows 3.1) TrueType fonts.

The vector fonts are virtually obsolete. The characters in these fonts were composed of simple lines, but these lines did not define filled areas. The vector fonts had the benefit of being scaleable to any size, but the characters often looked anemic.

TrueType fonts are outline fonts with characters defined by filled areas. TrueType fonts are scaleable; indeed the character definitions contain "hints" for avoiding rounding problems that could result in unsightly or unreadable text. It is with TrueType that Windows achieves a true WYSIWYG ("what you see is what you get") display of text on the video display that accurately matches printer output.

In bitmap fonts, each character is defined by an array of bits that correspond to the pixels of the video display. Bitmaps fonts can be scaleable to larger sizes, but they look jagged as a result. Bitmap fonts are often tweaked by their designers to be more easily readable on the video display. Thus, Windows uses bitmap fonts for the text that appears in title bars, menus, buttons, and dialog boxes.

The bitmap font that you get in a default device context is known as the system font. You can obtain a handle to this font by calling the GetStockObject function with the identifier SYSTEM_FONT. The KEYVIEW1 program elects to use a fixed-pitch version of the system font, denoted by SYSTEM_FIXED_FONT. Another alternative in the GetStockObject function is OEM_FIXED_FONT.

These three fonts have typeface names of (respectively) System, FixedSys, and Terminal. A program can use the typeface name to refer to the font in a CreateFont or CreateFontIndirect function call. These three fonts are stored in two sets of three files in the FONTS subdirectory of the Windows directory. The particular set of files that Windows uses depends on whether you've elected to display "Small Fonts" or "Large Fonts" in the Display applet of the Control Panel (that is, whether you want Windows to assume that the video display has a 96 dpi resolution or a 120 dpi resolution). This is all summarized in the following table:
GetStockObject Identifier Typeface Name Small Font File Large Font File
SYSTEM_FONT System VGASYS.FON 8514SYS.FON
SYSTEM_FIXED_FONT FixedSys VGAFIX.FON 8514FIX.FON
OEM_FIXED_FONT Terminal VGAOEM.FON 8514OEM.FON

In the file names, "VGA" refers to the Video Graphics Array, the video adapter that IBM introduced in 1987. It was IBM's first PC video adapter to have a pixel display size of 640 by 480. If you select Small Fonts from the Display applet in the Control Panel (meaning that you want Windows to assume that the video display has a resolution of 96 dpi), Windows uses the filenames beginning with "VGA" for these three fonts. If you select Large Fonts (meaning that you want a resolution of 120 dpi), Windows uses the filenames beginning with "8514." The 8514 was another video adapter that IBM introduced in 1987, and it had a maximum display size of 1024 by 768.

Windows does not want you to see these files. The files have the system and hidden file attributes set, and if you use the Windows Explorer to view the contents of your FONTS subdirectory, you won't see them at all, even if you've elected to view system and hidden files. Use the Find option from the Tools menu to search for files with a specification of *.FON. From there, you can double-click the filename to see what the font characters look like.

For many standard controls and user interface items, Windows doesn't use the System font. Instead, it uses a font with the typeface name MS Sans Serif. (MS stands for Microsoft.) This is also a bitmap font. The file (named SSERIFE.FON) contains fonts based on a 96-dpi video display, with point sizes of 8, 10, 12, 14, 18, and 24. You can get this font by using the DEFAULT_GUI_FONT identifier in GetStockObject. The point size Windows uses will be based on the display resolution you've selected in the Display applet of the Control Panel.

So far, I've mentioned four of the identifiers you can use with GetStockObject to obtain a font for use in a device context. There are three others: ANSI_FIXED_FONT, ANSI_VAR_FONT, and DEVICE_DEFAULT_FONT. To begin approaching the problem of the keyboard and character displays, let's take a look at all the stock fonts in Windows. The program that displays the fonts is named STOKFONT and is shown in Figure 6-5.

Figure 6-5. The STOKFONT program.

STOKFONT.C

/*-----------------------------------------
   STOKFONT.C -- Stock Font Objects
                 (c) Charles Petzold, 1998
  -----------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("StokFont") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     
     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 ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("Program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Stock Fonts"),
                          WS_OVERLAPPEDWINDOW | WS_VSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

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

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static struct
     {
          int     idStockFont ;
          TCHAR * szStockFont ;
     }
     stockfont [] = { OEM_FIXED_FONT,      "OEM_FIXED_FONT",
                      ANSI_FIXED_FONT,     "ANSI_FIXED_FONT",    
                      ANSI_VAR_FONT,       "ANSI_VAR_FONT",
                      SYSTEM_FONT,         "SYSTEM_FONT",
                      DEVICE_DEFAULT_FONT, "DEVICE_DEFAULT_FONT",
                      SYSTEM_FIXED_FONT,   "SYSTEM_FIXED_FONT",
                      DEFAULT_GUI_FONT,    "DEFAULT_GUI_FONT" } ;

     static int  iFont, cFonts = sizeof stockfont / sizeof stockfont[0] ;
     HDC         hdc ;
     int         i, x, y, cxGrid, cyGrid ;
     PAINTSTRUCT ps ;
     TCHAR       szFaceName [LF_FACESIZE], szBuffer [LF_FACESIZE + 64] ;
     TEXTMETRIC  tm ;
     
     switch (message)
     {
     case WM_CREATE:
          SetScrollRange (hwnd, SB_VERT, 0, cFonts - 1, TRUE) ;
          return 0 ;

     case WM_DISPLAYCHANGE:
          InvalidateRect (hwnd, NULL, TRUE) ;
          return 0 ;

     case WM_VSCROLL:
          switch (LOWORD (wParam))
          {
          case SB_TOP:            iFont = 0 ;                break ;
          case SB_BOTTOM:         iFont = cFonts - 1 ;       break ;
          case SB_LINEUP:
          case SB_PAGEUP:         iFont -= 1 ;               break ;
          case SB_LINEDOWN:
          case SB_PAGEDOWN:       iFont += 1 ;               break ;
          case SB_THUMBPOSITION:  iFont = HIWORD (wParam) ;  break ;
          }
          iFont = max (0, min (cFonts - 1, iFont)) ;
          SetScrollPos (hwnd, SB_VERT, iFont, TRUE) ;
          InvalidateRect (hwnd, NULL, TRUE) ;
          return 0 ;
     case WM_KEYDOWN:
          switch (wParam)
          {
          case VK_HOME: SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0) ;      break ;
          case VK_END:  SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0) ;   break ;
          case VK_PRIOR:
          case VK_LEFT:
          case VK_UP:   SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0) ;   break ;
          case VK_NEXT: 
          case VK_RIGHT:
          case VK_DOWN: SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0) ; break ;
          }
          return 0 ;

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

          SelectObject (hdc, GetStockObject (stockfont[iFont].idStockFont)) ;
          GetTextFace (hdc, LF_FACESIZE, szFaceName) ;
          GetTextMetrics (hdc, &tm) ;
          cxGrid = max (3 * tm.tmAveCharWidth, 2 * tm.tmMaxCharWidth) ;
          cyGrid = tm.tmHeight + 3 ;

          TextOut (hdc, 0, 0, szBuffer, 
               wsprintf (szBuffer, TEXT (" %s: Face Name = %s, CharSet = %i"),
                         stockfont[iFont].szStockFont, 
                         szFaceName, tm.tmCharSet)) ;

          SetTextAlign (hdc, TA_TOP | TA_CENTER) ;

               // vertical and horizontal lines

          for (i = 0 ; i < 17 ; i++)
          {
               MoveToEx (hdc, (i + 2) * cxGrid,  2 * cyGrid, NULL) ;
               LineTo   (hdc, (i + 2) * cxGrid, 19 * cyGrid) ;

               MoveToEx (hdc,      cxGrid, (i + 3) * cyGrid, NULL) ;
               LineTo   (hdc, 18 * cxGrid, (i + 3) * cyGrid) ;
          }
               // vertical and horizontal headings

          for (i = 0 ; i < 16 ; i++)
          {
               TextOut (hdc, (2 * i + 5) * cxGrid / 2, 2 * cyGrid + 2, szBuffer,
                    wsprintf (szBuffer, TEXT ("%X-"), i)) ;

               TextOut (hdc, 3 * cxGrid / 2, (i + 3) * cyGrid + 2, szBuffer,
                    wsprintf (szBuffer, TEXT ("-%X"), i)) ;
          }
               // characters

          for (y = 0 ; y < 16 ; y++)
          for (x = 0 ; x < 16 ; x++)
          {
               TextOut (hdc, (2 * x + 5) * cxGrid / 2, 
                                 (y + 3) * cyGrid + 2, szBuffer,
                    wsprintf (szBuffer, TEXT ("%c"), 16 * x + y)) ;
          }

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

This program is fairly simple. It uses the scroll bar and cursor movement keys to let you select one of the seven stock fonts to display. The program displays the 256 characters of the font in a grid. The headings at the top and left of the grid show the hexadecimal values of the character codes.

At the top of the client area, STOKFONT shows the identifier it uses to select the font using the GetStockObject function. It also displays the typeface name of the font obtained from the GetTextFace function and the tmCharSet field of the TEXTMETRIC structure. This "character set identifier" turns out to be crucial in understanding how Windows deals with foreign-language versions of Windows.

If you run STOKFONT under the American English version of Windows, the first screen you'll see shows you the font obtained by using the OEM_FIXED_FONT identifier with the GetStockObject function. This is shown in Figure 6-6.

Click to view at full size.

Figure 6-6. The OEM_FIXED_FONT in the U.S. version of Windows.

In this character set (as in all the others in this chapter), you'll see some ASCII. But remember that ASCII is a 7-bit code that defines displayable characters for codes 0x20 through 0x7E. By the time IBM developed the original IBM PC the 8-bit byte had been firmly established, so a full 8 bits could be used for character codes. IBM decided to extend the ASCII character set with a bunch of line- and block-drawing characters, accented letters, Greek letters, math symbols, and some miscellany. Many character-mode MS-DOS programs used the line-drawing characters in their on-screen displays, and many MS-DOS programs used some of the extended characters in their files.

This particular character set posed a problem for the original developers of Windows. On the one hand, the line- and block-drawing characters are not needed in Windows because Windows has a complete graphics programming language. The 48 codes used for these characters could better be used for additional accented letters required by many Western European languages. On the other hand, the IBM character set was definitely a standard that couldn't be ignored completely.

So, the original developers of Windows decided to support the IBM character set but to relegate it to secondary importance—mostly for old MS-DOS applications that ran in a window and for Windows programs that needed to use files created by MS-DOS applications. Windows applications do not use the IBM character set, and over the years it has faded in importance. Still, however, if you need it you can use it. In this context, "OEM" means "IBM."

(Be aware that foreign-language versions of Windows do not necessarily support the same OEM character set as the American English version does. Other countries had their own MS-DOS character sets. That's a whole subject in itself, but not one for this book.)

Because the IBM character set was deemed inappropriate for Windows, a different extended character set was selected. This is called the "ANSI character set," referring to the American National Standards Institute, but it's actually an ISO (International Standards Organization) standard, namely standard 8859. It's also known as Latin 1, Western European, or code page 1252. Figure 6-7 shows one version of the ANSI character set—the system font in the American English version of Windows.

Click to view at full size.

Figure 6-7. The SYSTEM_FONT in the U.S. version of Windows.

The thick vertical bars indicate codes for which characters are not defined. Notice that codes 0x20 through 0x7E are once again ASCII. Also, the ASCII control characters (0x00 through 0x1F, and 0x7F) are not associated with displayable characters. This is as it should be.

The codes 0xC0 through 0xFF make the ANSI character set important to foreign-language versions of Windows. These codes provide 64 characters commonly found in Western European languages. The character 0xA0, which looks like a space, is actually defined as a nonbreaking space, such as the space in "WW II."

I say this is "one version" of the ANSI character set because of the presence of the characters for codes 0x80 through 0x9F. The fixed-pitch system font includes only two of these characters, as shown in Figure 6-8.

Click to view at full size.

Figure 6-8. The SYSTEM_FIXED_FONT in the U.S. version of Windows.

In Unicode, codes 0x0000 through 0x007F are the same as ASCII, codes 0x0080 through 0x009F duplicate control characters 0x0000 through 0x001F, and codes 0x00A0 through 0x00FF are the same as the ANSI character set used in Windows.

If you run the German version of Windows, you'll get the same ANSI character sets when you call GetStockObject with the SYSTEM_FONT or SYSTEM_FIXED_FONT identifiers. This is true of other Western European versions of Windows as well. The ANSI character set was designed to have all the characters that are required in these languages.

However, when you run the Greek version of Windows, the default character set is not the same. Instead, the SYSTEM_FONT is that shown in Figure 6-9.

Click to view at full size.

Figure 6-9. The SYSTEM_FONT in the Greek version of Windows.

The SYSTEM_FIXED_FONT has the same characters. Notice the codes from 0xC0 through 0xFF. These codes contain uppercase and lowercase letters from the Greek alphabet. When you're running the Russian version of Windows, the default character set is shown in Figure 6-10.

Click to view at full size.

Figure 6-10. The SYSTEM_FONT in the Russian version of Windows.

Again, notice that uppercase and lowercase letters of the Cyrillic alphabet occupy codes 0xC0 and 0xFF.

Figure 6-11 shows the SYSTEM_FONT from the Japanese version of Windows. The characters from 0xA5 through 0xDF are all part of the katakana alphabet.

Click to view at full size.

Figure 6-11. The SYSTEM_FONT in the Japanese version of Windows.

The Japanese system font shown in Figure 6-11 is different from those shown previously because it is actually a double-byte character set (DBCS) called Shift-JIS. (JIS stands for Japanese Industrial Standard.) Most of the character codes from 0x81 through 0x9F and from 0xE0 through 0xFF are really just the first byte of a 2-byte code. The second byte is usually in the range 0x40 through 0xFC. (See Appendix G in Nadine Kano's book for a complete table of these codes.)

So now we can see where the problem is in KEYVIEW1: If you have the Greek keyboard layout installed and you type "abcde," regardless of the version of Windows you're running, Windows generates WM_CHAR messages with the character codes 0xE1, 0xE2, 0xF8, 0xE4, and 0xE5. But these character codes will correspond to the characters a, b, y, d, and e only if you're running the Greek version of Windows with the Greek system font.

If you have the Russian keyboard layout installed and you type "abcde," regardless of the version of Windows you're running, Windows generates WM_CHAR messages with the character codes 0xF4, 0xE8, 0xF1, 0xE2, and 0xF3. But these character codes will correspond to the characters ô, è, ñ, â, and ó only if you're running the Russian version of Windows or another language that uses the Cyrillic alphabet, and you're using the Cyrillic system font.

If you have the German keyboard layout installed and you type the = key (or the key in that same position) followed by the a, e, i, o, or u key, regardless of the version of Windows you're running, Windows generates WM_CHAR messages with the character codes 0xE1, 0xE9, 0xED, 0xF3, and 0xFA. Only if you're running a Western European or American version of Windows, which means that you have the Western European system font, will these character codes correspond to the characters á, é, í, ó, or ú.

If you have the American English keyboard layout installed, you can type anything on your keyboard and Windows will generate WM_CHAR messages with character codes that correctly match to the proper characters.

What About Unicode?

I claimed in Chapter 2 that Unicode support in Windows NT helps out in writing programs for an international market. Let's try compiling KEYVIEW1 with the UNICODE identifier defined and running it under various versions of Windows NT. (On this book's companion disc, the Unicode version of KEYVIEW1 is located in the DEBUG directory.)

If the UNICODE identifier is defined when the program is compiled, the "KeyView1" window class is registered with the RegisterClassW rather than the RegisterClassA function. This means that any message delivered to WndProc that has character or text data will use 16-bit characters rather than 8-bit characters. In particular, the WM_CHAR message will deliver a 16-bit character code rather than an 8-bit character code.

Run the Unicode version of KEYVIEW1 under the American English version of Windows NT. I'll assume you've installed at least the other three keyboard layouts we've been experimenting with—that is, German, Greek, and Russian.

With the American English version of Windows NT and either the English or German keyboard layout installed, the Unicode version of KEYVIEW1 will appear to work the same as the non-Unicode version. It will receive the same character codes (all of which will be 0xFF or lower in value) and display the same correct characters. This is because the first 256 characters of Unicode are the same as the ANSI character set used in Windows.

Now switch to the Greek keyboard layout, and type "abcde." The WM_CHAR messages will have the Unicode character codes 0x03B1, 0x03B2, 0x03C8, 0x03B4, and 0x03B5. Note that for the first time we're seeing character codes with values higher than 0xFF. These Unicode character codes correspond to the Greek letters a, b, y, d, and e. However, all five characters are displayed as solid blocks! This is because the SYSTEM_FIXED_FONT only has 256 characters.

Now switch to the Russian keyboard layout, and type "abcde." KEYVIEW1 displays WM_CHAR messages with the Unicode character codes 0x0444, 0x0438, 0x0441, 0x0432, and 0x0443, corresponding to the Cyrillic characters ô, è, ñ, â, and ó. Once again, however, all five characters are displayed as solid blocks.

In short, where the non-Unicode version of KEYVIEW1 displayed incorrect characters, the Unicode version of KEYVIEW1 displays solid blocks, indicating that the current font does not have that particular character. I hesitate to say that the Unicode version of KEYVIEW1 represents an "improvement" over the non-Unicode version, but it does. The non-Unicode version displays characters that are not correct. The Unicode version does not.

The differences between the Unicode and non-Unicode versions of KEYVIEW1 are mostly in two areas.

First, the WM_CHAR message is accompanied by a 16-bit character code rather than an 8-bit character code. The 8-bit character code in the non-Unicode version of KEYVIEW1 could have different meanings depending what keyboard layout is active. A code of 0xE1 could mean á if it came from the German keyboard, a if it came from the Greek keyboard, and á if it came from the Russian keyboard. In the Unicode version of the program, the 16-bit character code is totally unambiguous. The á character is 0x00E1, the a character is 0x03B1, and the á character is 0x0431.

Second, the Unicode TextOutW function displays characters based on 16-bit character codes rather than on the 8-bit character codes of the non-Unicode TextOutA function. Because these 16-bit character codes are totally unambiguous, GDI can determine whether the font currently selected in the device context is capable of displaying each character.

Running the Unicode version of KEYVIEW1 under the American version of Windows NT is somewhat deceptive, because it appears as if GDI is simply displaying character codes in the range 0x0000 through 0x00FF and not those above 0x00FF. That is, it appears as if there's a simple one-to-one mapping between the character codes and the 256 characters of the system font.

However, if you install the Greek or Russian versions of Windows NT, you'll discover that this is not the case. For example, if you install the Greek version of Windows NT, the American English, German, Greek, and Russian keyboards will generate the same Unicode character codes as the American version of Windows NT. However, the Greek version of Windows NT will not display German-accented characters or Russian characters because these characters are not in the Greek system font. Similarly, the Russian version of Windows NT will not display the German-accented characters or Greek characters because these characters are not in the Russian system font.

Where the Unicode version of KEYVIEW1 makes the most dramatic difference is under the Japanese version of Windows NT. You enter Japanese characters from the IME and they display correctly. The only problem is formatting: because the Japanese characters are often visually complex, they are displayed twice as wide as other characters.

TrueType and Big Fonts

The bitmap fonts that we've been using (with the exception of the fonts in the Japanese version of Windows) contain a maximum of 256 characters. This is to be expected, because the format of the bitmap font file goes back to the early days of Windows when character codes were assumed to be mere 8-bit values. That's why when we use the SYSTEM_FONT or the SYSTEM_FIXED_FONT, there are always some characters from some languages that we can't display properly. (The Japanese system font is a bit different because it's a double-byte character set; most of the characters are actually stored in TrueType Collection files with a filename extension of .TCC.)

TrueType fonts can contain more than 256 characters. Not all TrueType fonts have more than 256 characters, but the ones shipped with Windows 98 and Windows NT do. Or rather, they do if you've installed multilanguage support. In the Add/Remove Programs applet of the Control Panel, click the Windows Setup tab and make sure Multilanguage Support is checked. This multilanguage support involves five character sets: Baltic, Central European, Cyrillic, Greek, and Turkish. The Baltic character set is used for Estonian, Latvian, and Lithuanian. The Central European character set is used for Albanian, Czech, Croatian, Hungarian, Polish, Romanian, Slovak, and Slovenian. The Cyrillic character set is used for Bulgarian, Belarusian, Russian, Serbian, and Ukrainian.

The TrueType fonts shipped with Windows 98 support those five character sets, plus the Western European (ANSI) character set that is used for virtually all other languages except those in the Far East (Chinese, Japanese, and Korean). TrueType fonts that support multiple character sets are sometimes referred to as "big fonts." The word "big" in this context does not refer to the size of the characters, but to their quantity.

You can take advantage of big fonts even in a non-Unicode program, which means that you can use big fonts to display characters in several different alphabets. However, you need to go beyond the GetStockObject function in obtaining a font to select into a device context.

The functions CreateFont and CreateFontIndirect create a logical font, similar to the way CreatePen creates a logical pen and CreateBrush creates a logical brush. CreateFont has 14 arguments that describe the font you want to create. CreateFontIndirect has one argument, but that argument is a pointer to a LOGFONT structure, which has 14 fields that correspond to the arguments of the CreateFont function. I'll discuss these functions in more detail in Chapter 17. For now, we'll look at the CreateFont function, but we'll focus on only a couple arguments. All the other arguments can be set to zero.

If you need a fixed-pitch font (as we've been using for the KEYVIEW1 program), set the thirteenth argument to CreateFont to FIXED_PITCH. If you need a font of a nondefault character set (as we will be needing), set the ninth argument to CreateFont to something called the "character set ID." This character set ID will be one of the following values defined in WINGDI.H. I've added comments that indicate the code pages associated with these character sets:

#define ANSI_CHARSET            0      // 1252 Latin 1 (ANSI)
#define DEFAULT_CHARSET         1
#define SYMBOL_CHARSET          2
#define MAC_CHARSET             77
#define SHIFTJIS_CHARSET        128    // 932 (DBCS, Japanese)
#define HANGEUL_CHARSET         129    // 949 (DBCS, Korean)
#define HANGUL_CHARSET          129    // "                "
#define JOHAB_CHARSET           130    // 1361 (DBCS, Korean)
#define GB2312_CHARSET          134    // 936 (DBCS, Simplified Chinese)
#define CHINESEBIG5_CHARSET     136    // 950 (DBCS, Traditional Chinese)
#define GREEK_CHARSET           161    // 1253 Greek
#define TURKISH_CHARSET         162    // 1254 Latin 5 (Turkish)
#define VIETNAMESE_CHARSET      163    // 1258 Vietnamese
#define HEBREW_CHARSET          177    // 1255 Hebrew
#define ARABIC_CHARSET          178    // 1256 Arabic
#define BALTIC_CHARSET          186    // 1257 Baltic Rim
#define RUSSIAN_CHARSET         204    // 1251 Cyrillic (Slavic)
#define THAI_CHARSET            222    // 874 Thai
#define EASTEUROPE_CHARSET      238    // 1250 Latin 2 (Central Europe)
#define OEM_CHARSET             255    // Depends on country

Why does Windows have two different numbers—a character set ID and a code page ID—to refer to the same character sets? It's just one of the confusing quirks in Windows. Notice that the character set ID requires only 1 byte of storage, which is the size of the character set field in the LOGFONT structure. (Back in the Windows 1.0 days, memory and storage space were limited and every byte counted.) Notice that many different MS-DOS code pages are used in other countries, but only one character set ID—OEM_CHARSET—is used to refer to the MS-DOS character set.

You'll also notice that these character set values agree with the "CharSet" value shown on the top line of the STOKFONT program. In the American English version of Windows, we saw stock fonts that had character set IDs of 0 (ANSI_CHARSET) and 255 (OEM_CHARSET). We saw 161 (GREEK_CHARSET) in the Greek version of Windows, 204 (RUSSIAN_CHARSET) in the Russian version, and 128 (SHIFTJIS_CHARSET) in the Japanese version.

In the code above, DBCS stands for double-byte character set, which is used in the Far East versions of Windows. Other versions of Windows do not support DBCS fonts, so you can't use those character set IDs.

CreateFont returns an HFONT value—a handle to a logical font. You can select this font into a device context using SelectObject. You must eventually delete every logical font you create by calling DeleteObject.

The other part of the big font solution is the WM_INPUTLANGCHANGE message. Whenever you change the keyboard layout using the popup menu in the desktop tray, Windows sends your window procedure the WM_INPUTLANGCHANGE message. The wParam message parameter is the character set ID of the new keyboard layout.

The KEYVIEW2 program shown in Figure 6-12 implements logic to change the font whenever the keyboard layout changes.

Figure 6-12. The KEYVIEW2 program.

KEYVIEW2.C

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

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("KeyView2") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     
     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 ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Keyboard Message Viewer #2"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{
     static DWORD dwCharSet = DEFAULT_CHARSET ;            
     static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar ;
     static int   cLinesMax, cLines ;
     static PMSG  pmsg ;
     static RECT  rectScroll ;
     static TCHAR szTop[] = TEXT ("Message        Key       Char     ")
                            TEXT ("Repeat Scan Ext ALT Prev Tran") ;
     static TCHAR szUnd[] = TEXT ("_______        ___       ____     ")
                            TEXT ("______ ____ ___ ___ ____ ____") ;

     static TCHAR * szFormat[2] = { 
          
               TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),
               TEXT ("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;

     static TCHAR * szYes  = TEXT ("Yes") ;
     static TCHAR * szNo   = TEXT ("No") ;
     static TCHAR * szDown = TEXT ("Down") ;
     static TCHAR * szUp   = TEXT ("Up") ;

     static TCHAR * szMessage [] = { 
                         TEXT ("WM_KEYDOWN"),    TEXT ("WM_KEYUP"), 
                         TEXT ("WM_CHAR"),       TEXT ("WM_DEADCHAR"), 
                         TEXT ("WM_SYSKEYDOWN"), TEXT ("WM_SYSKEYUP"), 
                         TEXT ("WM_SYSCHAR"),    TEXT ("WM_SYSDEADCHAR") } ;
     HDC          hdc ;
     int          i, iType ;
     PAINTSTRUCT  ps ;
     TCHAR        szBuffer[128], szKeyName [32] ;
     TEXTMETRIC   tm ;

     switch (message)
     {
     case WM_INPUTLANGCHANGE:
          dwCharSet = wParam ;
                                   // fall through
     case WM_CREATE:
     case WM_DISPLAYCHANGE:
     
               // Get maximum size of client area

          cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;
          cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;

              // Get character size for fixed-pitch font

          hdc = GetDC (hwnd) ;

          SelectObject (hdc, CreateFont (0, 0, 0, 0, 0, 0, 0, 0,
                                   dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ; 
               
          GetTextMetrics (hdc, &tm) ;
          cxChar = tm.tmAveCharWidth ;
          cyChar = tm.tmHeight ;

          DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
          ReleaseDC (hwnd, hdc) ;

               // Allocate memory for display lines
          if (pmsg)
               free (pmsg) ;

          cLinesMax = cyClientMax / cyChar ;
          pmsg = malloc (cLinesMax * sizeof (MSG)) ;
          cLines = 0 ;
                                   // fall through
     case WM_SIZE:
          if (message == WM_SIZE)
          {
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
          }
               // Calculate scrolling rectangle

          rectScroll.left   = 0 ;
          rectScroll.right  = cxClient ;
          rectScroll.top    = cyChar ;
          rectScroll.bottom = cyChar * (cyClient / cyChar) ;

          InvalidateRect (hwnd, NULL, TRUE) ;

          if (message == WM_INPUTLANGCHANGE)
               return TRUE ;

          return 0 ;
          
     case WM_KEYDOWN:
     case WM_KEYUP:
     case WM_CHAR:
     case WM_DEADCHAR:
     case WM_SYSKEYDOWN:
     case WM_SYSKEYUP:
     case WM_SYSCHAR:
     case WM_SYSDEADCHAR: 

               // Rearrange storage array

          for (i = cLinesMax - 1 ; i > 0 ; i--)
          {
               pmsg[i] = pmsg[i - 1] ;
          }
               // Store new message
          pmsg[0].hwnd = hwnd ;
          pmsg[0].message = message ;
          pmsg[0].wParam = wParam ;
          pmsg[0].lParam = lParam ;

          cLines = min (cLines + 1, cLinesMax) ;

               // Scroll up the display

          ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;

          break ;        // ie, call DefWindowProc so Sys messages work
          
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

          SelectObject (hdc, CreateFont (0, 0, 0, 0, 0, 0, 0, 0,
                                   dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ; 
          
          SetBkMode (hdc, TRANSPARENT) ;
          TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;
          TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;

          for (i = 0 ; i < min (cLines, cyClient / cyChar - 1) ; i++)
          {
               iType = pmsg[i].message == WM_CHAR ||
                       pmsg[i].message == WM_SYSCHAR ||
                       pmsg[i].message == WM_DEADCHAR ||
                       pmsg[i].message == WM_SYSDEADCHAR ;

               GetKeyNameText (pmsg[i].lParam, szKeyName, 
                               sizeof (szKeyName) / sizeof (TCHAR)) ;

               TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,
                        wsprintf (szBuffer, szFormat [iType],
                             szMessage [pmsg[i].message 
- WM_KEYFIRST],                   
                             pmsg[i].wParam,
                             (PTSTR) (iType ? TEXT (" ") : szKeyName),
                             (TCHAR) (iType ? pmsg[i].wParam : ` `),
                             LOWORD (pmsg[i].lParam),
                             HIWORD (pmsg[i].lParam) & 0xFF,
                             0x01000000 & pmsg[i].lParam ? szYes  : szNo,
                             0x20000000 & pmsg[i].lParam ? szYes  : szNo,
                             0x40000000 & pmsg[i].lParam ? szDown : szUp,
                             0x80000000 & pmsg[i].lParam ? szUp   : szDown)) ;
          }
          DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;

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

Notice that KEYVIEW2 clears the screen and reallocates its storage space whenever the keyboard input language changes. There are two reasons for this: First, because KEYVIEW2 isn't being specific about the font it wants, the size of the font characters can change when the input language changes. The program needs to recalculate some variables based on the new character size. Second, KEYVIEW2 doesn't retain the character set ID in effect at the time it receives each character message. Thus, if the keyboard input language changed and KEYVIEW2 needed to redraw its client area, all the characters would be displayed with the new font.

I'll discuss fonts and character sets more in Chapter 17. If you'd like to research internationalization issues more, you can find documentation at /Platform SDK/Windows Base Services/International Features, but much essential information is also located in /Platform SDK/Windows Base Services/General Library/String Manipulation.