6.4 键盘消息和字符集

 摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P192

        本章后面的范例程序都是有缺陷的。它们不总是能在所有的 Windows 版本下正确运行。它们的缺陷不是被故意嵌入代码的;的确,也许你永远不会注意到它们。只有在某些不同的键盘语言和布局间转换,以及在多字节字符集的 Windows 远东版上运行时,才会发现它们的缺陷。所以我们不愿称它们为“bug”。

        但是,当在 Windows NT 下运行且使用 Unicode 编译时,程序将执行得非常好。在第 2 章我们也提到过这个问题,且论证了为什么 Unicode 在简化国际化工作时如此重要。

6.4.1  KEYVIEW1 程序

        了解键盘国际化问题的第一步是检查 Windows 传递给窗口过程的键盘和字符消息的内容。KEYVIEW1 程序对此会有所帮助。此程序在客户区显示了 Windows 传递给窗口过程的八种不同键盘消息的所有信息。

[cpp]  view plain  copy
  1. /*-------------------------------------------------------- 
  2.    KEYVIEW1.C -- Displays Keyboard and Character Messages 
  3.                  (c) Charles Petzold, 1998 
  4.   --------------------------------------------------------*/  
  5.   
  6. #include <windows.h>  
  7.   
  8. LRESULT CALLBACK WndProc (HWNDUINTWPARAMLPARAM) ;  
  9.   
  10. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  11.                     PSTR szCmdLine, int iCmdShow)  
  12. {  
  13.      static TCHAR szAppName[] = TEXT ("KeyView1") ;  
  14.      HWND         hwnd ;  
  15.      MSG          msg ;  
  16.      WNDCLASS     wndclass ;  
  17.   
  18.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;  
  19.      wndclass.lpfnWndProc   = WndProc ;  
  20.      wndclass.cbClsExtra    = 0 ;  
  21.      wndclass.cbWndExtra    = 0 ;  
  22.      wndclass.hInstance     = hInstance ;  
  23.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;  
  24.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;  
  25.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  26.      wndclass.lpszMenuName  = NULL ;  
  27.      wndclass.lpszClassName = szAppName ;  
  28.   
  29.      if (!RegisterClass (&wndclass))  
  30.      {  
  31.           MessageBox (NULL, TEXT ("This program requires Windows NT!"),  
  32.                       szAppName, MB_ICONERROR) ;  
  33.           return 0 ;  
  34.      }  
  35.   
  36.      hwnd = CreateWindow (szAppName, TEXT ("Keyboard Message Viewer #1"),  
  37.                           WS_OVERLAPPEDWINDOW,  
  38.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  39.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  40.                           NULL, NULL, hInstance, NULL) ;  
  41.   
  42.      ShowWindow (hwnd, iCmdShow) ;  
  43.      UpdateWindow (hwnd) ;  
  44.   
  45.      while (GetMessage (&msg, NULL, 0, 0))  
  46.      {  
  47.           TranslateMessage (&msg) ;  
  48.           DispatchMessage (&msg) ;  
  49.      }  
  50.      return msg.wParam ;  
  51. }  
  52.   
  53. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  54. {  
  55.      static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar ;  
  56.      static int   cLinesMax, cLines ;  
  57.      static PMSG  pmsg ;  
  58.      static RECT  rectScroll ;  
  59.      static TCHAR szTop[] = TEXT ("Message        Key       Char     ")  
  60.                             TEXT ("Repeat Scan Ext ALT Prev Tran") ;  
  61.      static TCHAR szUnd[] = TEXT ("_______        ___       ____     ")  
  62.                             TEXT ("______ ____ ___ ___ ____ ____") ;  
  63.   
  64.      static TCHAR * szFormat[2] = {  
  65.   
  66.                TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),  
  67.                TEXT ("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;  
  68.   
  69.      static TCHAR * szYes  = TEXT ("Yes") ;  
  70.      static TCHAR * szNo   = TEXT ("No") ;  
  71.      static TCHAR * szDown = TEXT ("Down") ;  
  72.      static TCHAR * szUp   = TEXT ("Up") ;  
  73.   
  74.      static TCHAR * szMessage [] = {  
  75.                          TEXT ("WM_KEYDOWN"),    TEXT ("WM_KEYUP"),  
  76.                          TEXT ("WM_CHAR"),       TEXT ("WM_DEADCHAR"),  
  77.                          TEXT ("WM_SYSKEYDOWN"), TEXT ("WM_SYSKEYUP"),  
  78.                          TEXT ("WM_SYSCHAR"),    TEXT ("WM_SYSDEADCHAR") } ;  
  79.      HDC          hdc ;  
  80.      int          i, iType ;  
  81.      PAINTSTRUCT  ps ;  
  82.      TCHAR        szBuffer[128], szKeyName [32] ;  
  83.      TEXTMETRIC   tm ;  
  84.   
  85.      switch (message)  
  86.      {  
  87.      case WM_CREATE:  
  88.      case WM_DISPLAYCHANGE:  
  89.   
  90.                // Get maximum size of client area  
  91.   
  92.           cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;  
  93.           cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;  
  94.   
  95.               // Get character size for fixed-pitch font  
  96.   
  97.           hdc = GetDC (hwnd) ;  
  98.   
  99.           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;  
  100.           GetTextMetrics (hdc, &tm) ;  
  101.           cxChar = tm.tmAveCharWidth ;  
  102.           cyChar = tm.tmHeight ;  
  103.   
  104.           ReleaseDC (hwnd, hdc) ;  
  105.   
  106.                // Allocate memory for display lines  
  107.   
  108.           if (pmsg)  
  109.                free (pmsg) ;  
  110.   
  111.           cLinesMax = cyClientMax / cyChar ;  
  112.           pmsg = (PMSG)malloc (cLinesMax * sizeof (MSG)) ;  
  113.           cLines = 0 ;  
  114.                                    // fall through  
  115.      case WM_SIZE:  
  116.           if (message == WM_SIZE)  
  117.           {  
  118.                cxClient = LOWORD (lParam) ;  
  119.                cyClient = HIWORD (lParam) ;  
  120.           }  
  121.                // Calculate scrolling rectangle  
  122.   
  123.           rectScroll.left   = 0 ;  
  124.           rectScroll.right  = cxClient ;  
  125.           rectScroll.top    = cyChar ;  
  126.           rectScroll.bottom = cyChar * (cyClient / cyChar) ;  
  127.   
  128.           InvalidateRect (hwnd, NULL, TRUE) ;  
  129.           return 0 ;  
  130.   
  131.      case WM_KEYDOWN:  
  132.      case WM_KEYUP:  
  133.      case WM_CHAR:  
  134.      case WM_DEADCHAR:  
  135.      case WM_SYSKEYDOWN:  
  136.      case WM_SYSKEYUP:  
  137.      case WM_SYSCHAR:  
  138.      case WM_SYSDEADCHAR:  
  139.   
  140.                // Rearrange storage array  
  141.   
  142.           for (i = cLinesMax - 1 ; i > 0 ; i--)  
  143.           {  
  144.                pmsg[i] = pmsg[i - 1] ;  
  145.           }  
  146.                // Store new message  
  147.   
  148.           pmsg[0].hwnd = hwnd ;  
  149.           pmsg[0].message = message ;  
  150.           pmsg[0].wParam = wParam ;  
  151.           pmsg[0].lParam = lParam ;  
  152.   
  153.           cLines = min (cLines + 1, cLinesMax) ;  
  154.   
  155.                // Scroll up the display  
  156.   
  157.           ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;  
  158.   
  159.           break ;        // ie, call DefWindowProc so Sys messages work  
  160.   
  161.      case WM_PAINT:  
  162.           hdc = BeginPaint (hwnd, &ps) ;  
  163.   
  164.           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;  
  165.           SetBkMode (hdc, TRANSPARENT) ;  
  166.           TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;  
  167.           TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;  
  168.   
  169.           for (i = 0 ; i < min (cLines, cyClient / cyChar - 1) ; i++)  
  170.           {  
  171.                iType = pmsg[i].message == WM_CHAR ||  
  172.                        pmsg[i].message == WM_SYSCHAR ||  
  173.                        pmsg[i].message == WM_DEADCHAR ||  
  174.                        pmsg[i].message == WM_SYSDEADCHAR ;  
  175.   
  176.                GetKeyNameText (pmsg[i].lParam, szKeyName,  
  177.                                sizeof (szKeyName) / sizeof (TCHAR)) ;  
  178.   
  179.                TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,  
  180.                         wsprintf (szBuffer, szFormat [iType],  
  181.                              szMessage [pmsg[i].message - WM_KEYFIRST],  
  182.                              pmsg[i].wParam,  
  183.                              (PTSTR) (iType ? TEXT (" ") : szKeyName),  
  184.                              (TCHAR) (iType ? pmsg[i].wParam : ' '),  
  185.                              LOWORD (pmsg[i].lParam),  
  186.                              HIWORD (pmsg[i].lParam) & 0xFF,  
  187.                              0x01000000 & pmsg[i].lParam ? szYes  : szNo,  
  188.                              0x20000000 & pmsg[i].lParam ? szYes  : szNo,  
  189.                              0x40000000 & pmsg[i].lParam ? szDown : szUp,  
  190.                              0x80000000 & pmsg[i].lParam ? szUp   : szDown)) ;  
  191.           }  
  192.           EndPaint (hwnd, &ps) ;  
  193.           return 0 ;  
  194.   
  195.      case WM_DESTROY:  
  196.           PostQuitMessage (0) ;  
  197.           return 0 ;  
  198.      }  
  199.      return DefWindowProc (hwnd, message, wParam, lParam) ;  
  200. }  

        KEYVIEW1 程序展示了它的窗口过程接收到的每一个击键和字符消息的内容。它将此消息存储在 MSG 结构数组中。数组的大小基于最大化窗口的大小和等宽的系统字体。如果用户在程序运行的时候调整视频显示的大小(在此种情况下,KEYVIEW1 程序会收到一个 WM_DISPLAYCHANGE 消息),该数组将被重新产生。KEYVIEW1 程序采用标准 C 的 malloc 函数为此数组分配内存。

        图 6-4 显示了在输入字符“Windows”之后, KEYVIEW1 程序的屏幕显示。第一列显示了键盘消息。第二列显示了击键消息的虚拟键代码和键名称。这是使用 GetKeyNameText 函数获得的。第三列(标注为“Char”)显示了字符消息的十六进制字符代码和字符本身,剩下的六列显示了 lParam 消息参数的 6 个字段。

        为便于分栏显示该消息,KEYVIEW1 程序采用了等宽字体。像上一章中讨论的那样,这需要调用 GetStockObject 和 SelectObject 函数:

[cpp]  view plain  copy
  1. SelectObject (hdc, GetStockObject(SYSTEM_FIXED_FONT));  
为了辨识该九列数据,KEYVIEW1 在客户区的上部加了标题。标题加有下划线。虽然可以产生带下划线的字体,但此处采用了不同的方法。我定义了两个字符串变量,分别为 szTop(含有文字)和 szUnd(含有下划线),然后在处理 WM_PAINT 消息时,同时在窗口上部的同一位置显示两个字符串。通常,Windows 以“opaque”(不透明)模式显示文字,这意味着 Windows 在显示字符的时候抹去了字符背景。此处将导致第二个字符串(szUnd)抹去第一个字符串(szTop)。为了阻止它的发生,转换设备环境到“TRANSPARENT”(透明)模式:

[cpp]  view plain  copy
  1. SetBkMode (hdc, TRANSPARENT);  
这种加下划线的方法只有在使用等宽字体时才可行。否则,这种在字符下面的下划线将不会和字符等宽

图 6-4 KEYVIEW1 的屏幕显示

6.4.2  非英语键盘问题

        如果你运行 Windows 的美国英语版本,安装不同的键盘布局就可以让你输入英文以外的一些语言了。你可在控制面板的【键盘】程序中安装外语键盘布局。选中【语言】标签,然后单击【添加】。为了解死键怎样工作,你可能需要安装德语键盘。我也将讨论俄语和希腊语键盘布局,所以你可能也需要安装这些键盘。如果俄语和希腊语键盘布局不在【键盘】程序显示的列表中,你可能需要安装多语言支持。在控制面板中选择【添加或删除程序】并选择【Windows 组件】,确认选中了多语言支持。在任何情况下,如果你有原始的 Windows CD-ROM 会使这些改变更便利。

        当你安装完其他的键盘布局后,将会在任务栏右侧的通知区中看到双字母码的蓝色框。如果默认是英语,它显示为“EN”。当你单击此图标时,可得到已安装的键盘布局的列表。你可以通过单击想要的键盘布局来改变当前活动程序使用的键盘。这种改变仅影响当前的活动程序。

6.4.3  字符集和字体

        KEYVIEW1 的问题是字体问题:在屏幕上显示字符的字体与它从键盘接收到的字符码不一致。因此,让我们来看看这些字体。

        正如我将在第 17 章详细讨论的那样,Windows 支持 3 种字体——位图字体(bitmap fonts)、矢量字体(vector fonts)和(Windows 3.1 中开始采用的)TrueType 字体。

        矢量字体其实已经过时了。这些字体中的字符由简单的线条组成,但这些线条没有定义填充区域。矢量字体可以缩放至任意大小,但字符看起来有些单薄。

        TrueType 字体是由填充区域来定义字符的轮廓字体。TrueType 字体是可缩放的,而且字体定义中包含的“提示”信息能避免造成难看的或不可读的文本的舍入问题。Windows 利用 TrueType 字体达到了真正的所见即所得(what you see is what you get, WYSIWYG),使得在显示器上显示的字体和打印机输出的字体是准确匹配的。

        位图字体中,每一个字符是由对应于显示器的像素的一组位定义位置定义的。位图字体可以放大到较大的尺寸,但字体看起来有锯齿。在设计位图字体时通常会考虑到它在显示器上的效果,所以一般在显示器上看起来很舒服。因此,Windows 在标题栏、菜单、按钮和对话框中使用位图字体。

        你在默认的设备环境中得到的位图字体称为系统字体。你可通过调用带有 SYSTEM_FONT 标识符的 GetStockObject 函数获得此字体的句柄。KEYVIEW1 程序选择使用系统字体的等宽字体版本,用 SYSTEM_FIXED_FONT 表示。GetStockObject 函数的另一个选择是 OEM_FIXED_FONT。

        这三种字体都有字样名称,分别是 System,FixedSys 和 Terminal。程序可以在 CreateFont 或者 CreateFontIndirect 函数调用中使用字样名称来指定字体。这三种字体存储在 Windows 目录的 FONTS 子目录下的两组两个文件中。Windows 使用哪一组文件取决于你在控制面板的【显示】程序中选择显示“小字体”还是“大字体”(就是说,你希望 Windows 采用的是分辨率为 96dpi 还是 120dpi 的视频显示)。这些都总结在下表中:

GetStockObject 标识符字样名称小字体文件大字体文件
 SYSTEM_FONT System VGASYS.FON 8514SYS.FON
 SYSTEM_FIXED_FONT FixedSys VGAFIX.FON 8514FIX.FON
 OEM_FIXED_FONT Terminal VGAOEM.FON 8514OEM.FON

        在文件名利,“VGA”指视频图形阵列,是一种 IBM 在 1987 年推出的视频显示卡。这是 IBM 第一块可显示 640 * 480 像素的个人计算机视频显示卡。如果你在控制面板的【显示】程序中选择了小字体(意味着你希望 Windows 采用图形分辨率为 96dpi 的视频显示),则 Windows 采用以 “VGA”开头的文件名。如果你选择大字体(意味着你希望 Windows 采用图形分辨率为 120dpi 的视频显示),Windows 则采用以“8514”开头的文件名。8514 是 IBM 在 1987 年推出的另一块视频显示卡,它的最大显示尺寸为 1024 * 768。

        Windows 不希望你看到这些文件。它们的文件属性设置为系统和隐藏。如果你使用【资源管理器】查看 FONT 子目录的内容,即使选择了查看系统和隐藏文件,也不会看见它们。你从【工具】菜单中使用【查找】搜索文件名满足 *.FON 的文件。从那里,你可双击文件名查看该字符是什么样子。

        对许多标准控件和用户界面组件来讲,Windows 不采用系统字体,而采用字体名为 MS Sans Serif 的字体(MS 代表微软)。这是一种位图字体。名为 SSERIFE.FON 的文件包含用于分辨率为 96dpi 的视频显示的字体,字号为 8, 10, 12, 14, 18 和 24 磅。你可在 GetStockObject 中采用 DEFAULT_GUI_FONT 标识符获得此字体。Windows 使用的字号大小取决于你在控制面板的【显示】程序中设置的显示分辨率。

        到目前为止,我已经提到了可在 GetStockObject 中使用的四种标识符,你能用它们获得在设备环境中使用的字体。还有其他三种标识符:ANSI_FIXED_FONT,ANSI_VAR_FONT 和 DEVICE_DEFAULT_FONT。为了开始处理键盘和字符显示问题,让我们看一下 Windows 中所有备用字体。显示这些字体的程序是 STOKFONT。

[cpp]  view plain  copy
  1. /*-------------------------------------------------------- 
  2.    STOKFONT.C -- Stock Font Objects 
  3.                  (c) Charles Petzold, 1998 
  4.   --------------------------------------------------------*/  
  5.   
  6. #include <windows.h>  
  7.   
  8. LRESULT CALLBACK WndProc (HWNDUINTWPARAMLPARAM) ;  
  9.   
  10. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  11.                     PSTR szCmdLine, int iCmdShow)  
  12. {  
  13.      static TCHAR szAppName[] = TEXT ("StokFont") ;  
  14.      HWND         hwnd ;  
  15.      MSG          msg ;  
  16.      WNDCLASS     wndclass ;  
  17.   
  18.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;  
  19.      wndclass.lpfnWndProc   = WndProc ;  
  20.      wndclass.cbClsExtra    = 0 ;  
  21.      wndclass.cbWndExtra    = 0 ;  
  22.      wndclass.hInstance     = hInstance ;  
  23.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;  
  24.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;  
  25.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  26.      wndclass.lpszMenuName  = NULL ;  
  27.      wndclass.lpszClassName = szAppName ;  
  28.   
  29.      if (!RegisterClass (&wndclass))  
  30.      {  
  31.           MessageBox (NULL, TEXT ("This program requires Windows NT!"),  
  32.                       szAppName, MB_ICONERROR) ;  
  33.           return 0 ;  
  34.      }  
  35.   
  36.      hwnd = CreateWindow (szAppName, TEXT ("Stock Fonts"),  
  37.                           WS_OVERLAPPEDWINDOW | WS_VSCROLL,  
  38.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  39.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  40.                           NULL, NULL, hInstance, NULL) ;  
  41.   
  42.      ShowWindow (hwnd, iCmdShow) ;  
  43.      UpdateWindow (hwnd) ;  
  44.   
  45.      while (GetMessage (&msg, NULL, 0, 0))  
  46.      {  
  47.           TranslateMessage (&msg) ;  
  48.           DispatchMessage (&msg) ;  
  49.      }  
  50.      return msg.wParam ;  
  51. }  
  52.   
  53. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  54. {  
  55.      static struct  
  56.      {  
  57.          int idStockFont;  
  58.          TCHAR * szStockFont;  
  59.      }  
  60.      stockfont [] = {   OEM_FIXED_FONT,         "OEM_FIXED_FONT",  
  61.                         ANSI_FIXED_FONT,        "ANSI_FIXED_FONT",  
  62.                         ANSI_VAR_FONT,          "ANSI_VAR_FONT",  
  63.                         SYSTEM_FONT,            "SYSTEM_FONT",  
  64.                         DEVICE_DEFAULT_FONT,    "DEVICE_DEFAULT_FONT",  
  65.                         SYSTEM_FIXED_FONT,      "SYSTEM_FIXED_FONT",  
  66.                         DEFAULT_GUI_FONT,       "DEFAULT_GUI_FONT"};  
  67.   
  68.      static int     iFont, cFonts = sizeof stockfont / sizeof stockfont[0];  
  69.      HDC            hdc;  
  70.      int            i, x, y, cxGrid, cyGrid;  
  71.      PAINTSTRUCT    ps;  
  72.      TCHAR          szFaceName [LF_FACESIZE], szBuffer[LF_FACESIZE + 64];  
  73.      TEXTMETRIC     tm;  
  74.   
  75.      switch (message)  
  76.      {  
  77.      case WM_CREATE:  
  78.          SetScrollRange(hwnd, SB_VERT, 0, cFonts - 1, TRUE);  
  79.          return 0;  
  80.   
  81.      case WM_DISPLAYCHANGE:  
  82.           InvalidateRect(hwnd, NULL, TRUE);  
  83.           return 0;  
  84.   
  85.      case WM_VSCROLL:  
  86.           switch (LOWORD(wParam))  
  87.           {  
  88.               case SB_TOP:           iFont = 0; break;  
  89.               case SB_BOTTOM:        iFont = cFonts - 1; break;  
  90.               case SB_LINEUP:  
  91.               case SB_PAGEUP:        iFont -= 1; break;  
  92.               case SB_LINEDOWN:  
  93.               case SB_PAGEDOWN:      iFont += 1; break;  
  94.               case SB_THUMBPOSITION: iFont = HIWORD(wParam); break;  
  95.           }  
  96.           iFont = max(0, min(cFonts - 1, iFont));  
  97.           SetScrollPos(hwnd, SB_VERT, iFont, TRUE);  
  98.           InvalidateRect(hwnd, NULL, TRUE);  
  99.           return 0;  
  100.   
  101.      case WM_KEYDOWN:  
  102.           switch(wParam)  
  103.           {  
  104.               case VK_HOME: SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0); break;  
  105.               case VK_END:  SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0); break;  
  106.               case VK_PRIOR:  
  107.               case VK_LEFT:  
  108.               case VK_UP:   SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); break;  
  109.               case VK_NEXT:  
  110.               case VK_RIGHT:  
  111.               case VK_DOWN: SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); break;  
  112.           }  
  113.           return 0;  
  114.   
  115.      case WM_PAINT:  
  116.           hdc = BeginPaint (hwnd, &ps) ;  
  117.   
  118.           SelectObject (hdc, GetStockObject (stockfont[iFont].idStockFont)) ;  
  119.           GetTextFace (hdc, LF_FACESIZE, szFaceName) ;  
  120.           GetTextMetrics(hdc, &tm);  
  121.           cxGrid = max(3 * tm.tmAveCharWidth, 2 * tm.tmMaxCharWidth);  
  122.           cyGrid = tm.tmHeight + 3;  
  123.   
  124.           TextOut(hdc, 0, 0, szBuffer, wsprintf(szBuffer, TEXT("%s:Face Name = %s,  CharSet = %i"),  
  125.                                                 stockfont[iFont].szStockFont,  
  126.                                                 szFaceName, tm.tmCharSet));  
  127.   
  128.           SetTextAlign(hdc, TA_TOP | TA_CENTER);  
  129.   
  130.                             // vertical and horizontal lines  
  131.   
  132.           for (i = 0; i < 17; ++ i)  
  133.           {  
  134.               MoveToEx(hdc, (i + 2) * cxGrid, 2 * cyGrid, NULL);  
  135.               LineTo(hdc, (i + 2) * cxGrid, 19 * cyGrid);  
  136.   
  137.               MoveToEx(hdc, cxGrid, (i + 3) * cyGrid, NULL);  
  138.               LineTo(hdc, 18 * cxGrid, (i + 3) * cyGrid);  
  139.           }  
  140.                                 // vertical and horizontal headings  
  141.   
  142.           for (i = 0; i < 16; ++ i)  
  143.           {  
  144.               TextOut(hdc, (2 * i + 5)* cxGrid / 2, 2 * cyGrid + 2, szBuffer,  
  145.                       wsprintf(szBuffer, TEXT("%X-"), i));  
  146.   
  147.               TextOut(hdc, 3 * cxGrid / 2, (i + 3) * cyGrid + 2, szBuffer,  
  148.                       wsprintf(szBuffer, TEXT("-%X"), i));  
  149.           }  
  150.                                 // characters  
  151.   
  152.           for (y = 0; y < 16; ++ y)  
  153.             for (x = 0; x < 16; ++ x)  
  154.           {  
  155.                 TextOut(hdc, (2 * x + 5) * cxGrid / 2,  
  156.                                 (y + 3) * cyGrid + 2, szBuffer,  
  157.                         wsprintf(szBuffer, TEXT("%c"), 16 * x + y));  
  158.           }  
  159.           EndPaint (hwnd, &ps) ;  
  160.           return 0 ;  
  161.   
  162.      case WM_DESTROY:  
  163.           PostQuitMessage (0) ;  
  164.           return 0 ;  
  165.      }  
  166.      return DefWindowProc (hwnd, message, wParam, lParam) ;  
  167. }  

        这个程序还算简单。它使用滚动条和光标移动键让你选择七种备用字体中的一种来显示。此程序在一个栅格里显示一种字体的 256 个字符。栅格顶部和左侧显示了字符码的十六进制值。

        在客户区的顶部,STOKFONT 显示了调用 GetStockObject 函数选择字体时使用的标识符。它也显示了用 GetTextFace 函数获得的字样名字和 TEXTMETRIC 结构的 tmCharSet 字段。此“字符集标识符”对理解 Windows 怎样处理它的非英语版本是至关重要的。

        如果你在 Windows 的美国英语版本下运行 STOKFONT,你将会看到的第一个屏幕显示了用 GetStockObject 函数的 OEM_FIXED_FONT 标识符获得的字体,如图 6-6 所示(Windows 的中国版本与之不同):

图 6-6  Windows 的美国英语版本下的 OEM_FIXED_FONT

        在此字符集中(像本章中的其他部分一样),你将看到一些 ASCII。但请记住 ASCII 是 7 位码,定义了编码从 0x20 到 0x7E 的可显示字符。在 IBM 开发出最初的 IBM 个人计算机时,就已经严格地规定一个字节为 8 位。因此字符编码采用了 8 位码。IBM 决定使用一些线状和块状的图形字符、带重音的字母、希腊字母、数字符号和其他的符号来扩展 ASCII 字符集。许多文本模式的 MS-DOS 程序在屏幕显示上会使用线状图形字符,还有许多 MS-DOS 程序在它们的文件中使用到一些扩展字符。

        这个特定的字符集给 Windows 最初的开发者造成了问题。一方面,Windows 不需要线状和块状图形字符,因为 Windows 有完整的图形编程语言。这些字符使用的 48 个码可以用来定义西欧语言所需要的重音字母。但是另一方面,IBM 字符集又已经成为了一个不可忽略的标准。

        所以,Windows 最初的开发者决定支持 IBM 字符集,但把它的重要性降到第二位。它们大多用于在窗口中运行的旧的 MS-DOS 应用程序和需要使用由 MS-DOS 应用程序创建的文件的 Windows 程序。Windows 应用程序不使用 IBM 字符集,并且随着时间的推移,它也慢慢丧失了重要性。但如果你需要使用它,也仍然可以使用。这里的“OEM”指“IBM”。

        (注意,Windows 的非英语版本不一定支持美国英语版本采用的 OEM 字符集。其他国家有它们自己的 MS-DOS 字符集。这是个独立的主题,本书不会讨论。)

        因为 IBM 字符集不适用于 Windows,Windows 选择了另一个扩展字符集——ANSI 字符集。此字符集由美国国家标准化组织制定,但实质上是一个 ISO(International Standards Organization, 国际化标准化组织)标准,即 ISO 标准 8859。它也被称为Latin1,Western European 或代码页 1252。图 6-7 显示了 ANSI 字符集的一个版本——在 Windows 美国英语版本下的系统字体。

图 6-7  Windows 的美国英语版本下的 SYSTEM_FONT

        粗的竖条表明这些字符的代码未被定义。注意,代码 0x20 到 0x7E 仍然是 ASCII 码。此外,ASCII 控制字符(从 0x00 到 0x1F,以及 0x7F)依然不可显示。这些本应该是这样。

        ANSI 字符集中的代码 0xC0 到 0xFF 对 Windows 的非英语版本非常重要。这些代码提供了 64 个西欧语言中普遍使用的字符。字符 0xA0,看起来像一个空格,实质上被定义为一个不间断空格,就像字符串 “WW II” 中的空格一样

        我之所以说这只是 ANSI 字符集的“一个版本”,是因为代码 0x80 到 0x9F 的字符。等宽系统字体治保会这些字符中的两个,如图 6-8 所示。

        在 Unicode 中, 编码 0x0000 到 0x007F 同样是 ASCII,代码 0x0080 到 0x009F 复制了从 0x0000 到 0x001F 的控制字符,编码 0x00A0 到 0x00FF 同 Windows 中使用的 ANSI 字符集是一样的。

图 6-8  Windows 的美国英语版本下的 SYSTEM_FIXED_FONT

        如果你运行 Windows 的德语版本,当你调用含 SYSTEM_FONT 或 SYSTEM_FIXED_FONT 标识符的 GetStockObject 函数时,你将得到同样的 ANSI 字符集。对于 Windows 的其他西欧版本也同样是这样。ANSI 字符集被设计为包含了在这些语言中需要的所有字符。

        但是,当你运行 Windows 的希腊语版本时,默认的字符集是不同的。图 6-9 显示了它的 SYSTEM_FONT。

图 6-9  Windows 希腊语版本的 SYSTEM_FONT

        SYSTEM_FIXED_FONT 有同样的字符。但注意从 0xC0 到 0xFF 的编码,这些编码包含了希腊字母表中的大写字母和小写字母。当你运行 Windows 的俄语版本时,默认的字符设置如图 6-10 所示。

图 6-10  Windows 俄语版本的 SYSTEM_FONT

再次注意,西里尔字母表中的大写字母和小写字母编码也是从 0xC0 到 0xFF。

        图 6-11 显示的则是 Windows 的日语版本的 SYSTEM_FONT。从 0xA5 到 0xDF 的字符属于片假名字母表。

图 6-11  Windows 日语版本的 SYSTEM_FONT

        图 6-11 中显示的日语系统字体不同于前面显示的字体,因为它们其实是一种称为 Shift-JIS 的双字节字符集。(JIS 代表日本工业标准。)从 0x81 到 0x9F 与从 0xE0 到 0xFF 的大部分字符码实际上是双字节编码的第一个字节。第二个字节通常从 0x40 到 0xFC。(参考 Nadine Kano 的书的附录 G,那里有这些编码的完整列表。)

        所以现在我们能看到 KEYVIEW1 程序的问题在哪里:如果你安装了希腊语键盘布局,并输入“abcde”,那么不管你运行哪个版本的 Windows,Windows 都会产生字符码 0xE1, 0xF8, 0xE4 和 0xE5 的 WM_CHAR 消息。但是只有当你运行的是 Windows 的希腊语版本并采用希腊语系统字体时,这些字符码才对应于α,β,ψ,δ 和 ε 。

        如果你安装了俄语键盘布局并输入“abcde”,同样不管你运行哪个版本的 Windows,Windows 都会产生字符码为 0xF4, 0xE8, 0xF1, 0xE2 和 0xF3 的 WM_CHAR 消息。但是仅当你运行 Windows 的俄语或另一种使用西里尔字母表语言的版本,并采用西里尔系统字体时,这些字符码才对应于ф、и、с、в 和 у 。

        如果你安装了德语键盘布局,并依次输入“=”键(或同样位置的键)和a, e, i, o 或 u 键,那么不管你运行哪个版本的 Windows,Windows 均产生字符码为 0xE1, 0xE9, 0xED, 0xF3 或 0xFA 的 WM_CHAR 消息。但是仅当你运行 Windows 的西欧或者是美国版本,并采用西欧系统字体时,这些字符码才对应于á、é、í、ó 和 ú 。

        如果你安装了美国英语键盘布局,你就可以在你的键盘上输入任何字符,Windows 的 WM_CHAR 消息的字符码将正确地匹配合适的字符。

6.4.4  Unicode 解决方案

        我在第 2 章提到过,Windows NT 对 Unicode 编码的支持有助于编写适用于国际市场的程序。让我们试着编译定义了 UNICODE 标识符的 KEYVIEW1 程序,并在 Windows NT 的不同版本上运行。

        如果程序编译时已定义了 UNICODE 标识符,那么“KeyView1”窗口类将用 RegisterClassW 函数注册,而不是 RegisterClassA 函数。这意味着传递给 WndProc 的任何含字符或文本数据的消息将使用 16 位字符,而不是 8 位字符。特别是 WM_CHAR 消息将传递 16 位的字符码,而不是 8 位字符码。

        在 Windows NT 美国英语版本下运行 KEYVIEW1 程序的 Unicode 版本。我假定你至少已经安装了我们试验过的 3 种键盘布局——即德语、希腊语和俄语。

        在已安装了英语或德语键盘布局的 Windows NT 美国英语版本下,KEYVIEW1 程序的 Unicode 版本似乎和非 Unicode 版本一样正常工作。它将接收同样的字符码(0xFF 及所有低的值),并显示同样正确的字符。这是因为 Unicode 的前 256 个字符与 Windows 中使用的 ANSI 字符集是相同的。

        现在切换到希腊语键盘布局并输入“abcde”。WM_CHAR 消息将包含 Unicode 字符码 0x03B1, 0x03B2, 0x03C8, 0x03B4 和 0x03B5.注意,我们第一次看到值高于 0xFF 的字符码。这些 Unicode 字符码对应于希腊字母α,β,ψ,δ 和 ε 。但是,所有的 5 个字符显示为实心快!这是因为 SYSTEM_FIXED_FONT 仅包含 256 个字符。

       现在切换到俄语键盘布局并输入“abcde”。KEVIEW1 程序显示了 Unicode 字符码为 0x0444, 0x0438, 0x0441, 0x0432 和 0x0443 的 WM_CHAR 消息。这些字符码对应于西里尔字符φ、и、с、в 和 у 。但是,5 个字符又一次显示为实心块。

        简而言之,KEYVIEW1 程序的非 Unicode 版本显示为不正确字符的地方,Unicode 版本显示为实心块,这表明当前的字体不包含特殊字符。虽然我不愿承认 KEYVIEW1 程序的 Unicode 版本是非 Unicode 版本的“改进”,但它的确是的。非 Unicode 版本会显示错误的字符,而 Unicode 版本却不会。

        KEYVIEW1 程序的 Unicode 版本和非 Unicode 版本的不同主要表现在两个方面。

        首先,WM_CHAR 消息采用 16 位字符码而不是 8 位字符码。KEYVIEW1 程序的非 Unicode 版本所使用的  8 位字符码的不同含义取决于当前使用的键盘布局。0xE1 码在德语键盘下代表á,在希腊语键盘下代表α,在俄语键盘下代表б 。在 KEYVIEW1 程序中 Unicode 版本下,16 位字符码定义的字符是唯一的。á 字符是 0x00E1,α 字符是 0x03B1,б 字符是 0431。

        其次,Unicode 版本的 TextOutW 函数是基于 16 位字符码显示字符的,而不是非 Unicode 的 TextOutA 函数的 8 位字符码。因为 16 位字符码是无二义性的,所以 GDI 能根据设备环境当前选用的字体判定它是否有能力显示每一个字符

        在 Windows NT 美国版本下运行的 KEYVIEW1 程序的 Unicode 版本有点欺骗性,因为它看起来好像 GDI 仅简单地显示了从 0x0000 到 0x00FF 的字符码,而不是高于 0x00FF 的所有字符。也就是说,似乎在字符码和系统字体的 256 个字符间存在简单的一一映射。

        但是,如果你安装了 Windows NT 的希腊语或俄语版本,你将会发现情况不是这样。例如,如果你安装了 Windows NT 的希腊语版本,美国英语、德语、希腊语和日语键盘都将产生和 Windows NT 的美国版本同样的 Unicode 字符码。但是,Windows NT 的希腊语版将不能显示德语重音字符或俄语字符,因为希腊语系统字体不包含这些字符。同样地,Windows NT 的俄语版本将不能显示德语重音字符或希腊语字符,因为俄语系统字体中也不包含这些字符。

        KEYVIEW1 程序的 Unicode 版本的最大的不同体现在 Windows NT 的日语版本中。你从 IME 中输入日语字符,而它们能正确显示。唯一的问题是格式:因为日语字符通常是很复杂的,因此它们的显示宽度是其他字符的两倍。

6.4.5  TrueType 字体和大字体

        我们使用的位图字体(Windows 日语版的字体除外)最多可包含 256 个字符。这和我们预料的是一样的。因为位图字体文件的格式可追溯到 Windows 的早期,那时字符码仅有 8 位值。这也正是当我们使用 SYSTEM_FONT 或 SYSTEM_FIXED_FONT 时,总是有某些语言的某些字符不能正确显示的原因。(日语系统字体有一点不同,因为它是双字节字符集。事实上,大部分字符存储在 TrueType 集合文件中,扩展名为 .TTC。)

        TrueType 字体能包含的字符多于 256 个。并不是所有的 TrueType 字体都包含多于 256 个字符,只有 Windows 98 和 Windows NT 中采用的 TrueType 字体是这样的。或者更准确点说,安装了多语言支持的系统都是这样。在控制面板的【添加或删除程序】中,单击【Windows 组件】,确定多语言支持被选中。多语言支持包含五种字符集:波罗的海语中欧语西里尔语希腊语土耳其语波罗的海语字符集适用于爱沙尼亚语、拉脱维亚语和立陶宛语。中欧字符集适用于阿尔巴尼亚语、捷克语、克罗地亚语、匈牙利语、波兰语、罗马尼亚语、斯洛伐克语和斯洛文尼亚语。西里尔字符集适用于保加利亚语、白俄罗斯语、俄语、塞尔维亚语和乌克兰语。

        Windows 98 中使用的 TrueType 字体支持这五种字符集和适用于除远东(汉语、日语和朝鲜语)之外的所有其他语言的西欧(ANSI)字符集。支持多字符集的 TrueType 字体有时也称为“大字体”。这里的“大”不是指字符的尺寸,而是指字符的数量。

        你甚至能在非 Unicode 程序中使用大字体。这意味着你能使用大字体显示不同字母表中的字符。但是,为了获得设备环境中选择的字体,你需要使用 GetStockObject 函数和其他一些函数。

        CreateFont 函数和 CreateFontIndirect 函数可以创建逻辑字体,方法类似于 CreatePen 创建逻辑画笔和 CreateBrush 创建逻辑画刷。CreateFont 用于 14 个参数描述你想创建的字体。CreateFontIndirect 函数有一个参数,但此参数是一个指向 LOGFONT 结构的指针。LOGFONT 有 14 个字段一一对应着 CreateFont 函数中的 14 个参数。我将在第 17 章详细讨论这些函数。现在,我们来看一下 CreateFont 函数,但重点集中在两个参数上,其他参数设置为零。

        如果你需要等宽字体(就像我们在 KEYVIEW1 程序中使用的那样),就把 CreateFont 函数的第 13 个参数设置为 FIXED_PITCH。如果你需要非默认字符集中的字体(像我们将要使用的那样),就把 CreateFont 函数的第 9 个参数设置为某个“字符集 ID”。此字符集 ID 将是定义在 WINGDI.H 中的下列所有值中的一个。我已添加了注释,之处和这些字符集相关的代码页。

[cpp]  view plain  copy
  1. #define ANSI_CHARSET            0        // 1252 拉丁语 1 (ANSI)  
  2. #define DEFAULT_CHARSET            1       
  3. #define SYMBO_CHARSET            2  
  4. #define MAC_CHARSET                77  
  5. #define    SHIFTJIS_CHARSET        128        // 932 (DBCS, 日语)  
  6. #define HANGEUL_CHARSET            129        // 949 (DBCS, 朝鲜语)  
  7. #define HANGUL_CHARSET             129        // " "  
  8. #define JOHAB_CHARSET            130        // 1361 (DBCS, 朝鲜语)  
  9. #define GB2312_CHARSET            134        // 936 (DBCS, 简单中文)  
  10. #define CHINESEBIG5_CHARSET     136     // 950 (DBCS, 繁体中文)  
  11. #define GREEK_CHARSET            161     // 1253 希腊语  
  12. #define TURKISH_CHARSET            162     // 1254 拉丁语 5 (土耳其语)  
  13. #define VIETNAMESE_CHARSET        163     // 1258 越南语  
  14. #define HEBREW_CHARSET            177        // 1255 希伯来语  
  15. #define ARABIC_CHARSET            178        // 1256 阿拉伯语  
  16. #define BALTIC_CHARSET            186        // 1257 波罗的海语  
  17. #define RUSSIAN_CHARSET            204        // 1251 西里尔语 (斯拉夫语)  
  18. #define THAI_CHARSET            222        // 874 泰国语  
  19. #define EASTEUROPE_CHARSET        238        // 1250 拉丁语 2 (中欧)  
  20. #define OEM_CHARSET                255        // 取决于国家/地区  

        为什么 Windows 对同样一个字符集会有两个不同的 ID——字符集 ID 和代码页 ID?这仅仅是 Windows 的一个怪癖。注意,字符集 ID 仅需要一个字节的存储空间,这也是 LOGFONT 结构的“字符集”字段的大小。(回忆 Windows 1.0 的时候,内存和存储空间有限,因而每个字节都很重要。)请注意,许多不同的 MS-DOS 代码页用在其他国家或地区,但 MS-DOS 字符集中仅使用一种字符集 ID——OEM_CHARSET。

        你也注意到这些字符集的值同 STOKFONT 程序中显示在最顶部的“CharSet”值相同。在美国的英语版本中,我们看到备用字体的字符集 ID 是 0(ANSI_CHARSET)和 255(OEM_CHARSET)。Windows 的希腊语版本中是 161(GREEK_CHARSET),俄语版本中是 204(RUSSIAN_CHARSET),日语版本中是 128(SHIFTJIS_CHARSET)。

        在上面的代码中,DBCS 代表双字节字符集,它用于 Windows 的远东版本。Windows 的其他版本不支持 DBCS 字体,所以你不能使用这些字符集 ID。

        CreateFont 返回 HFONT 值——逻辑字体的句柄。你可以使用 SelectObject 函数把这种字体选入设备环境中。最终你必须调用 DeleteObject 函数删除你创建的每个逻辑字体。

        大字体解决方案的其他部分是 WM_INPUTLANGCHANGE 消息。只要你使用桌面通知区域的弹出菜单改变了键盘布局,Windows 就会把 WM_INPUTLANGCHANGE 消息传递给你的窗口过程。相应的 wParam 消息参数是新键盘布局的字符集 ID

        KEYVIEW2 程序显示了如何在键盘布局改变时相应地改变字体。

[cpp]  view plain  copy
  1. /*-------------------------------------------------------- 
  2.    KEYVIEW2.C -- Displays Keyboard and Character Messages 
  3.                  (c) Charles Petzold, 1998 
  4.   --------------------------------------------------------*/  
  5. #include <windows.h>  
  6.   
  7. LRESULT CALLBACK WndProc (HWNDUINTWPARAMLPARAM) ;  
  8.   
  9. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  10.                     PSTR szCmdLine, int iCmdShow)  
  11. {  
  12.      static TCHAR szAppName[] = TEXT ("KeyView2") ;  
  13.      HWND         hwnd ;  
  14.      MSG          msg ;  
  15.      WNDCLASS     wndclass ;  
  16.   
  17.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;  
  18.      wndclass.lpfnWndProc   = WndProc ;  
  19.      wndclass.cbClsExtra    = 0 ;  
  20.      wndclass.cbWndExtra    = 0 ;  
  21.      wndclass.hInstance     = hInstance ;  
  22.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;  
  23.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;  
  24.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  25.      wndclass.lpszMenuName  = NULL ;  
  26.      wndclass.lpszClassName = szAppName ;  
  27.   
  28.      if (!RegisterClass (&wndclass))  
  29.      {  
  30.           MessageBox (NULL, TEXT ("This program requires Windows NT!"),  
  31.                       szAppName, MB_ICONERROR) ;  
  32.           return 0 ;  
  33.      }  
  34.   
  35.      hwnd = CreateWindow (szAppName, TEXT ("Keyboard Message Viewer #2"),  
  36.                           WS_OVERLAPPEDWINDOW,  
  37.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  38.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  39.                           NULL, NULL, hInstance, NULL) ;  
  40.   
  41.      ShowWindow (hwnd, iCmdShow) ;  
  42.      UpdateWindow (hwnd) ;  
  43.   
  44.      while (GetMessage (&msg, NULL, 0, 0))  
  45.      {  
  46.           TranslateMessage (&msg) ;  
  47.           DispatchMessage (&msg) ;  
  48.      }  
  49.      return msg.wParam ;  
  50. }  
  51.   
  52. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  53. {  
  54.      static DWORD dwCharSet = DEFAULT_CHARSET;  
  55.      static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar ;  
  56.      static int   cLinesMax, cLines ;  
  57.      static PMSG  pmsg ;  
  58.      static RECT  rectScroll ;  
  59.      static TCHAR szTop[] = TEXT ("Message        Key       Char     ")  
  60.                             TEXT ("Repeat Scan Ext ALT Prev Tran") ;  
  61.      static TCHAR szUnd[] = TEXT ("_______        ___       ____     ")  
  62.                             TEXT ("______ ____ ___ ___ ____ ____") ;  
  63.   
  64.      static TCHAR * szFormat[2] = {  
  65.   
  66.                TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),  
  67.                TEXT ("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;  
  68.   
  69.      static TCHAR * szYes  = TEXT ("Yes") ;  
  70.      static TCHAR * szNo   = TEXT ("No") ;  
  71.      static TCHAR * szDown = TEXT ("Down") ;  
  72.      static TCHAR * szUp   = TEXT ("Up") ;  
  73.   
  74.      static TCHAR * szMessage [] = {  
  75.                          TEXT ("WM_KEYDOWN"),    TEXT ("WM_KEYUP"),  
  76.                          TEXT ("WM_CHAR"),       TEXT ("WM_DEADCHAR"),  
  77.                          TEXT ("WM_SYSKEYDOWN"), TEXT ("WM_SYSKEYUP"),  
  78.                          TEXT ("WM_SYSCHAR"),    TEXT ("WM_SYSDEADCHAR") } ;  
  79.      HDC          hdc ;  
  80.      int          i, iType ;  
  81.      PAINTSTRUCT  ps ;  
  82.      TCHAR        szBuffer[128], szKeyName [32] ;  
  83.      TEXTMETRIC   tm ;  
  84.   
  85.      switch (message)  
  86.      {  
  87.      case WM_INPUTLANGCHANGE:  
  88.         dwCharSet = wParam;  
  89.                                 // fall through  
  90.   
  91.      case WM_CREATE:  
  92.      case WM_DISPLAYCHANGE:  
  93.   
  94.                // Get maximum size of client area  
  95.   
  96.           cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;  
  97.           cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;  
  98.   
  99.               // Get character size for fixed-pitch font  
  100.   
  101.           hdc = GetDC (hwnd) ;  
  102.   
  103.           SelectObject (hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0,  
  104.                                         dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ;  
  105.           GetTextMetrics (hdc, &tm) ;  
  106.           cxChar = tm.tmAveCharWidth ;  
  107.           cyChar = tm.tmHeight ;  
  108.   
  109.           DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));  
  110.           ReleaseDC (hwnd, hdc) ;  
  111.   
  112.                // Allocate memory for display lines  
  113.   
  114.           if (pmsg)  
  115.                free (pmsg) ;  
  116.   
  117.           cLinesMax = cyClientMax / cyChar ;  
  118.           pmsg = (PMSG)malloc (cLinesMax * sizeof (MSG)) ;  
  119.           cLines = 0 ;  
  120.                                    // fall through  
  121.      case WM_SIZE:  
  122.           if (message == WM_SIZE)  
  123.           {  
  124.                cxClient = LOWORD (lParam) ;  
  125.                cyClient = HIWORD (lParam) ;  
  126.           }  
  127.                // Calculate scrolling rectangle  
  128.   
  129.           rectScroll.left   = 0 ;  
  130.           rectScroll.right  = cxClient ;  
  131.           rectScroll.top    = cyChar ;  
  132.           rectScroll.bottom = cyChar * (cyClient / cyChar) ;  
  133.   
  134.           InvalidateRect (hwnd, NULL, TRUE) ;  
  135.           return 0 ;  
  136.   
  137.      case WM_KEYDOWN:  
  138.      case WM_KEYUP:  
  139.      case WM_CHAR:  
  140.      case WM_DEADCHAR:  
  141.      case WM_SYSKEYDOWN:  
  142.      case WM_SYSKEYUP:  
  143.      case WM_SYSCHAR:  
  144.      case WM_SYSDEADCHAR:  
  145.   
  146.                // Rearrange storage array  
  147.   
  148.           for (i = cLinesMax - 1 ; i > 0 ; i--)  
  149.           {  
  150.                pmsg[i] = pmsg[i - 1] ;  
  151.           }  
  152.                // Store new message  
  153.   
  154.           pmsg[0].hwnd = hwnd ;  
  155.           pmsg[0].message = message ;  
  156.           pmsg[0].wParam = wParam ;  
  157.           pmsg[0].lParam = lParam ;  
  158.   
  159.           cLines = min (cLines + 1, cLinesMax) ;  
  160.   
  161.                // Scroll up the display  
  162.   
  163.           ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;  
  164.   
  165.           break ;        // ie, call DefWindowProc so Sys messages work  
  166.   
  167.      case WM_PAINT:  
  168.           hdc = BeginPaint (hwnd, &ps) ;  
  169.   
  170.           SelectObject (hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0,  
  171.                                         dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ;  
  172.   
  173.           SetBkMode (hdc, TRANSPARENT) ;  
  174.           TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;  
  175.           TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;  
  176.   
  177.           for (i = 0 ; i < min (cLines, cyClient / cyChar - 1) ; i++)  
  178.           {  
  179.                iType = pmsg[i].message == WM_CHAR ||  
  180.                        pmsg[i].message == WM_SYSCHAR ||  
  181.                        pmsg[i].message == WM_DEADCHAR ||  
  182.                        pmsg[i].message == WM_SYSDEADCHAR ;  
  183.   
  184.                GetKeyNameText (pmsg[i].lParam, szKeyName,  
  185.                                sizeof (szKeyName) / sizeof (TCHAR)) ;  
  186.   
  187.                TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,  
  188.                         wsprintf (szBuffer, szFormat [iType],  
  189.                              szMessage [pmsg[i].message - WM_KEYFIRST],  
  190.                              pmsg[i].wParam,  
  191.                              (PTSTR) (iType ? TEXT (" ") : szKeyName),  
  192.                              (TCHAR) (iType ? pmsg[i].wParam : ' '),  
  193.                              LOWORD (pmsg[i].lParam),  
  194.                              HIWORD (pmsg[i].lParam) & 0xFF,  
  195.                              0x01000000 & pmsg[i].lParam ? szYes  : szNo,  
  196.                              0x20000000 & pmsg[i].lParam ? szYes  : szNo,  
  197.                              0x40000000 & pmsg[i].lParam ? szDown : szUp,  
  198.                              0x80000000 & pmsg[i].lParam ? szUp   : szDown)) ;  
  199.           }  
  200.           DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));  
  201.           EndPaint (hwnd, &ps) ;  
  202.           return 0 ;  
  203.   
  204.      case WM_DESTROY:  
  205.           PostQuitMessage (0) ;  
  206.           return 0 ;  
  207.      }  
  208.      return DefWindowProc (hwnd, message, wParam, lParam) ;  
  209. }  

        请注意,KEYVIEW2 程序在键盘输入语言改变的时候,清空了屏幕,重新分配它的存储空间。这有两个原因。首先,KEYVIEW2 程序不清楚它具体使用的字体,当输入语言改变时,字体中字符的大小将改变。程序需要机遇新字符的大小重新计算一些变量。第二,KEYVIEW2 程序在接收每个字符消息时,不保留正在使用的字符集 ID。因此,如果键盘输入语言改变,KEYVIEW2 程序需要刷新它的客户区,所有的字符将会用新字体显示。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值