三、Windows键盘消息和字符集—字符集和字体
KEYLOOK1的问题是字体问题。用于在屏幕上显示字符的字体和键盘接收的字符代码不一致。因此,让我们看一下字体。
我将在第十七章进行详细讨论,Windows支持三类字体-点阵字体、向量字体和(从Windows 3.1开始的)TrueType字体。
事实上向量字体已经过时了。这些字体中的字符由简单的线段组成,但这些线段没有定义填入区域。向量字体可以较好地缩放到任意大小,但字符通常看上去有些单薄。
TrueType字体是定义了填入区域的文字轮廓字体。TrueType字体可缩放;而且该字符的定义包括「提示」,以消除可能带来的文字不可见或者不可读的圆整问题。使用TrueType字体,Windows就真正实现了WYSIWYG(「所见即所得」),即文字在视讯显示器显示与打印机输出完全一致。
在点阵字体中,每个字符都定义为与视讯显示器上的图素对应的位点阵。点阵字体可拉伸到较大的尺寸,但看上去带有锯齿。点阵字体通常被设计成方便在视讯显示器上阅读的字体。因此,Windows中的标题列、菜单、按钮和对话框的显示文字都使用点阵字体。
在内定的设备内容下获得的点阵字体称为系统字体。您可通过呼叫带有SYSTEM_FONT标识符的GetStockObject函数来获得字体句柄。KEYVIEW1程序选择使用SYSTEM_FIXED_FONT表示的等宽系统字体。GetStockObject函数的另一个选项是OEM_FIXED_FONT。
这三种字体有(各自的)字体名称-System、FixedSys和Terminal。程序可以在CreateFont或者CreateFontIndirect函数呼叫中使用字体名称来指定字体。这三种字体储存在两组放在Windows目录内的FONTS子目录下的三个文件中。Windows使用哪一组文件取决于「控制台」里的「显示器」是选择显示「小字体」还是「大字体」(亦即,您希望Windows假定视讯显示器是96 dpi的分辨率还是120 dpi的分辨率)。表6-14总结了所有的情况:
| 
					 表6-14  | 
			
	
| 
					 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」指的是视频图形数组(Video Graphics Array),IBM在1987年推出的显示卡。这是IBM第一块可显示640×480图素大小的PC显示卡。如果在「控制台」的「显示器」中选择了「小字体」(表示您希望Windows假定视讯显示的分辨率为96 dpi),则Windows使用的这三种字体文件名将以「VGA」开头。如果选择了「大字体」(表示您希望分辨率为120 dpi),Windows使用的文件名将以「8514」开头。8514是IBM在1987年推出的另一种显示卡,它的最大显示尺寸为1024×768。
Windows不希望您看到这些文件。这些文件的属性设定为系统和隐藏,如果用Windows Explorer来查看FONTS子目录的内容,您是不会看到它们的,即使选择了查看系统和隐藏文件也不行。从开始菜单选择「寻找」选项来寻找文件名满足 *.FON限定条件的文件。这时,您可以双击文件名来查看字体字符是些什么。
对于许多标准控件和使用者接口组件,Windows不使用系统字体。相反地,使用名称为MS Sans Serif的字体(「MS」代表Microsoft)。这也是一种点阵字体。文件(名为SSERIFE.FON)包含依据96 dpi视讯显示器的字体,点值为8、10、12、14、18和24。您可在GetStockObject函数中使用DEFAULT_GUI_FONT标识符来得到该字体。Windows使用的点值取决于「控制台」的「显示」中选择的显示分辨率。
到目前为止,我已提到四种标识符,利用这四种标识符,您可以用GetStockObject来获得用于设备内容的字体。还有三种其它字体标识符:ANSI_FIXED_FONT、ANSI_VAR_FONT和DEVICE_DEFAULT_FONT。为了开始处理键盘和字符显示问题,让我们先看一下Windows中的所有备用字体。显示这些字体的程序是STOKFONT,如程序6-3所示。
        
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) ;
        
}
        
这个程序相当简单。它使用滚动条和光标移动键让您选择显示七种备用字体之一。该程序在一个网格中显示一种字体的256个字符。顶部的标题和网格的左侧显示字符代码的十六进制值。
在显示区域的顶部,STOKFONT用GetStockObject函数显示用于选择字体的标识符。它还显示由GetTextFace函数得到的字体样式名称和TEXTMETRIC结构的tmCharSet字段。这个「字符集标识符」对理解Windows如何处理外语版本的Windows是非常重要的。
如果在美国英语版本的Windows中执行STOKFONT,那么您看到的第一个画面将显示使用OEM_FIXED_FONT标识符呼叫GetStockObject函数得到的字体。如图6-3所示。
	
在本字符集中(与本章其它部分一样),您将看到一些ASCII。但请记住ASCII是7位代码,它定义了从代码0x20到0x7E的可显示字符。到IBM开发出IBM PC原型机时,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,于是选择了另一种扩展字符集。此字符集称作「ANSI字符集」,由美国国家标准协会(American National Standards Institute)制定,但它实际上是ISO(International Standards Organization,国际标准化组织)标准,也就是ISO标准8859。它还称为Latin 1、Western European、或者代码页1252。图6-4显示了ANSI字符集的一个版本-美国英语版Windows的系统字体。
	
粗的垂直条表示这些字符代码没有定义。注意,代码0x20到0x7E还是ASCII。此外,ASCII控制字符(0x00到0x1F以及0x7F)并不是可显示字符。它们本应如此。
代码0xC0到0xFF使得ANSI字符集对外语版Windows来说非常重要。这些代码提供64个在西欧语言中普遍使用的字符。字符0xA0,看起来像空格,但实际上定义为非断开空格,例如「WW II」中的空格。
	之所以说这是ANSI字符集的「一个版本」,是因为存在代码0x80到0x9F的字符。等宽的系统字体只包括其中的两个字符,如图6-5所示。
在Unicode中,代码0x0000到0x007F与ASCII相同,代码0x0080到0x009F复制了0x0000到0x001F的控制字符,代码0x00A0到0x00FF与Windows中使用的ANSI字符集相同。
如果执行德语版的Windows,那么当您用SYSTEM_FONT或者SYSTEM_FIXED_FONT标识符来呼叫GetStockObject函数时会得到同样的ANSI字符集。其它西欧版Windows也是如此。ANSI字符集中含有这些语言所需要的所有字符。
不过,当您执行希腊版的Windows时,内定的字符集就改变了。相反地,SYSTEM_FONT如图6-6所示。
	
SYSTEM_FIXED_FONT有同样的字符。注意从0xC0到0xFF的代码。这些代码包含希腊字母表中的大写字母和小写字母。当您执行俄语版Windows时,内定的字符集如图6-7所示。
	
此外, 注意斯拉夫字母表中的大写和小写字母占用了代码0xC0和0xFF。
图6-8显示了日语版Windows的SYSTEM_FONT。从0xA5到0xDF的字符都是片假名字母表的一部分。
	
图6-8所示的日文系统字体不同于前面显示的那些,因为它实际上是双字节字符集(DBCS),称为「Shift-JIS」(「JIS」代表日本工业标准,Japanese Industrial Standard)。从0x81到0x9F以及从0xE0到0xFF的大多数字符代码实际上只是双字节代码的第一个字节,其第二个字节通常在0x40到0xFC的范围内(关于这些代码的完整表格,请参见Nadine Kano书中的附录G)。
现在,我们就可以看看KEYVIEW1中的问题在哪里:如果您安装了希腊键盘布局并键入『abcde』,不考虑执行的Windows版本,Windows将产生WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4和0xE5。但只有执行带有希腊系统字体的希腊版Windows时,这些字符代码才能与、、、和相对应。
如果您安装了俄语键盘布局并敲入『abcde』,不考虑所使用的Windows版本,Windows将产生WM_CHAR消息和字符代码0xF4、0xE8、0xF1、0xE2和0xF3。但只有在使用俄语版Windows或者使用斯拉夫字母表的其它语言版,并且使用斯拉夫系统字体时,这些字符代码才会与字符φ、и、с、в和у相对应。
如果您安装了德语键盘布局并按下=键(或者位于同一位置的键),然后按下a、e、i、o或者u键,不考虑使用的Windows版本,Windows将产生WM_CHAR消息和字符代码0xE1、0xE9、0xED、0xF3和0xFA。只有执行西欧版或者美国版的Windows时,也就是说有西欧系统字体,这些字符代码才会和字符amp;nbsp;á、é、í、ó和ú相对应。
如果安装了美国英语键盘布局,则您可在键盘上键入任何字符,Windows将产生WM_CHAR消息以及与字符正确匹配的字符代码。
	
	 
