三、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消息以及与字符正确匹配的字符代码。