键盘--外语键盘问题

来源:互联网 发布:贵阳网络电视台 编辑:程序博客网 时间:2024/06/02 22:01
 

外语键盘问题

如果您执行美国英语版本的Windows,那么您可安装不同的键盘布局,并输入外语。可以在 控制台键盘中安装外语键盘布局。选择 语系页面标签,按下新增 键。要查看死键的工作方式,您可能想安装「德语」键盘。此外,我还要讨论「俄语」和「希腊语」的键盘布局,因此您也可安装这些键盘布局。如果在「键盘」显示的列表中找不到「俄语」和「希腊语」的键盘布局,则需要安装多语系支持:从「控制台」中选择 新增/删除程序,然后选择 Windows安装程序页面卷标,确认选中 多语系支持复选框。在任何情况下,这些变更都需要原始的Windows光盘。

安装完其它键盘布局后,您将在工作列右侧的通知区看到一个带有两个字母代码的蓝色框。如果内定的是英语,那么这两个字母是「EN」。单击此图标,将得到所有已安装键盘布局的列表。从中单击需要的键盘布局即可更改目前活动程序的键盘。此改变只影响目前活动的程序。

现在开始进行实验。不使用UNICODE标识符定义来编译KEYVIEW1程序(在本书附带的光盘中,非Unicode版本的KEYVIEW1程序位于RELEASE子目录)。在美国英语版本的Windows下执行该程序,并输入字符『abcde』。 WM_CHAR消息与您所期望的一样:ASCII字符代码0x61、0x62、0x63、0x64和0x65以及字母a、b、c、d和e。

现在,KEYVIEW1还在执行,选择德语键盘布局。按下=键然后输入一个元音(a、e、i、o或者u)。=键将产生一个WM_DEADCHAR消息,元音产生一个WM_CHAR消息和(单独的)字符代码0xE1、0xE9、0xED、0xF3、0xFA和字符á、é、í、ó或ú。这就是死键的工作方式。

现在选择希腊键盘布局。输入『abcde』,您会得到什么?您将得到WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4、0xE5和字符á、狻ⅱ、?#160;和濉T谡饫镉行┳址荒苷废允尽D训滥挥Ω玫玫较@白帜副碇械淖帜嘎穑?/p>

现在切换到俄语键盘并重新输入『abcde』。现在您得到WM_CHAR消息和字符代码0xF4、0xE8、0xF1、0xE2和0xF3,以及字符簟ⅷāⅠ、?#160;和ó。而且,还是有些字母不能正常显示。您应从斯拉夫字母表中得到这些字母。

问题在于:您已经切换键盘以产生不同的字符代码,但您还没有将此切换通知GDI,好让GDI能选择适当的符号来显示解释这些字符代码。

如果您非常勇敢,还有可用的备用PC,并且是专业或全球版Microsoft Developer Network(MSDN)的订阅户,那么您也许想安装(例如)希腊版的Windows,您还可以把那四种键盘布局(英语、希腊语、德语和俄语)安装上去。现在执行KEYLOOK1,切换到英语键盘布局,然后输入『abcde』。您应得到ASCII字符代码0x61、0x62、0x63、0x64和0x65以及字符a、b、c、d和e(并且您可以放心:即使在希腊版,ASCII还是正常通行的)。

在希腊版的Windows中,切换到希腊键盘布局并输入『abcde』。您将得到WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4和0xE5。这与您在安装希腊键盘布局的英语版Windows中得到的字符代码相同。但现在显示的字符是、、、和。这些确实是小写的希腊字母alpha、beta、psi、delta和epsilon(gamma怎么了?是这样,如果使用希腊版的Windows,那么您将使用键帽上带有希腊字母的键盘。与英语c相对应的键正好是psi。gamma由与英语g相对应的键产生。您可在Nadine Kano编写的《Developing International Software for Windows 95 and Windows NT》的第587页看到完整的希腊字母表)。

继续在希腊版的Windows下运行KEYVIEW1,切换到德语键盘布局。输入『=』键,然后依次输入a、e、i、o和u。您将得到WM_CHAR消息和字符代码0xE1、0xE9、0xED、0xF3和0xFA。这些字符代码与安装德语键盘布局的英语版Windows中的一样。不过,显示的字符却是、、、和ϊ,而不是正确的á、é、í、ó和ú。

现在切换到俄语键盘并输入『abcde』。您会得到字符代码0xF4、0xE8、0xF1、0xE2和0xF3,这与安装俄语键盘的英语版Windows中得到的一样。不过,显示的字符是、、、和,而不是斯拉夫字母表中的字母。

您还可安装俄语版的Windows。现在您可以猜到,英语和俄语键盘都可以工作,而德语和希腊语则不行。

现在,如果您真的很勇敢,您还可安装日语版的Windows并执行KEYVIEW1。如果再依美国键盘输入,那么您将输入英语文字,一切似乎都正常。不过,如果切换到德语、希腊语或者俄语键盘布局,并且试著作上述介绍的任何练习,您将看到以点显示的字符。如果输入大写的字母-无论是带重音符号的德语字母、希腊语字母还是俄语字母-您将看到这些字母显示为日语中用于拼写外来语的片假名。您也许对输入片假名感兴趣,但那不是德语、希腊语或者俄语。

远东版本的Windows包括一个称作「输入法编辑器」(IME)的实用程序,该程序显示为浮动的工具列,它允许您用标准键盘输入象形文字,即汉语、日语和朝鲜语中使用的复杂字符。一般来说,输入一组字母后,组成的字符将显示在另一个浮动窗口内。然后按 Enter键,合成的字符代码就发送到了活动窗口(即KEYVIEW1)。KEYVIEW1几乎没什么响应-WM_CHAR消息带来的字符代码大于128,但这些代码没有意义(Nadine Kano的书中有许多关于使用IME的内容)。

这时,我们已经看到了许多KEYLOOK1显示错误字符的例子-当执行安装了俄语或希腊语键盘布局的英语版Windows时,当执行安装了俄语或德语键盘布局的希腊版Windows时,以及执行安装了德语、俄语或者希腊语键盘布局的俄语版Windows时,都是这样。我们也看到了从日语版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所示。

程序6-3 STOKFONT
        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所示。


 

图6-3 美国版Windows中的OEM_FIXED_FONT

在本字符集中(与本章其它部分一样),您将看到一些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的系统字体。


 

图6-4 美国版Windows中的SYSTEM_FONT

粗的垂直条表示这些字符代码没有定义。注意,代码0x20到0x7E还是ASCII。此外,ASCII控制字符(0x00到0x1F以及0x7F)并不是可显示字符。它们本应如此。

代码0xC0到0xFF使得ANSI字符集对外语版Windows来说非常重要。这些代码提供64个在西欧语言中普遍使用的字符。字符0xA0,看起来像空格,但实际上定义为非断开空格,例如「WW II」中的空格。

之所以说这是ANSI字符集的「一个版本」,是因为存在代码0x80到0x9F的字符。等宽的系统字体只包括其中的两个字符,如图6-5所示。


 

图6-5 美国版Windows中的SYSTEM_FIXED_FONT

在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所示。


 

图6-6 希腊版Windows中的SYSTEM_FONT

SYSTEM_FIXED_FONT有同样的字符。注意从0xC0到0xFF的代码。这些代码包含希腊字母表中的大写字母和小写字母。当您执行俄语版Windows时,内定的字符集如图6-7所示。


 

图6-7 俄语版Windows中的SYSTEM_FONT

此外, 注意斯拉夫字母表中的大写和小写字母占用了代码0xC0和0xFF。

图6-8显示了日语版Windows的SYSTEM_FONT。从0xA5到0xDF的字符都是片假名字母表的一部分。


 

图6-8 日语版Windows中的SYSTEM_FONT

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

Unicode怎么样?

我在第二章谈到过Windows NT支持的Unicode有助于为国际市场程序写作。让我们编译一下定义了UNICODE标识符的KEYVIEW1,并在不同版本的Windows NT下执行(在本书附带的光盘中,Unicode版的KEYVIEW1位于DEBUG目录中)。

如果程序编译时定义了UNICODE标识符,则「KeyView1」窗口类别就用RegisterClassW函数注册,而不是RegisterClassA函数。这意味着任何带有字符或文字数据的消息传递给WndProc时都将使用16位字符而不是8位字符。特别是WM_CHAR消息,将传递16位字符代码而不是8位字符代码。

请在美国英语版的Windows NT下执行Unicode版的KEYVIEW1。这里假定您已经安装了至少三种我们试验过的键盘布局-即德语、希腊语和俄语。

使用美国英语版的Windows NT,并安装了英语或者德语的键盘布局,Unicode版的KEYVIEW1在工作时将与非Unicode版相同。它将接收相同的字符代码(所有0xFF或者更低的值),并显示同样正确的字符。这是因为最初的256个Unicode字符与Windows中使用的ANSI字符集相同。

现在切换到希腊键盘布局,并键入『abcde』。WM_CHAR消息将含有Unicode字符代码0x03B1、 0x03B2、0x03C8、 0x03B4和0x03B5。注意,我们先看到的字符代码值比0xFF高。这些Unicode字符代码与希腊字母、、、d和相对应。不过,所有这五个字符都显示为方块!这是因为SYSTEM_FIXED_FONT只含有256个字符。

现在切换到俄语键盘布局,并键入『abcde』。KEYVIEW1显示WM_CHAR消息和Unicode字符代码0x0444、0x0438、0x0441、0x0432和0x0443,这些字符对应于斯拉夫字母φ、и、с、в和у。不过,所有这五个字母也显示为实心方块。

简言之,非Unicode版的KEYVIEW1显示错误字符的地方,Unicode版的KEYVIEW1就显示实心方块,以表示目前的字体没有那种特殊字符。虽然我不愿说Unicode版的KEYVIEW1是非Unicode版的改进,但事实确实如此。非Unicode版显示错误字符,而Unicode版不会这样。

Unicode和非Unicode版KEYVIEW1的不同之处主要在两个方面。

首先,WM_CHAR消息伴随一个16位字符代码,而不是8位字符代码。在非Unicode版本的KEYVIEW1中,8位字符代码的含义取决于目前活动的键盘布局。如果来自德语键盘,则0xE1代码表示á,如果来自希腊语键盘则代表,如果来自俄语键盘则代表。在Unicode版本程序中,16位字符代码的含义很明确:a字符是0x00E1,字符是0x03B1,而字符是0x0431。

第二,Unicode的TextOutW函数显示的字符依据16位字符代码,而不是非Unicode的TextOutA函数的8位字符代码。因为这些16位字符代码含义明确,GDI可以确定目前在设备内容中选择的字体是否可显示每个字符。

在美国英语版Windows NT下执行Unicode版的KEYVIEW1多少让人感到有些迷惑,因为它所显示的就好像GDI只显示了0x0000到0x00FF之间的字符代码,而没有显示高于0x00FF的代码。也就是说,只是在字符代码和系统字体中256个字符之间简单的一对一映射。

然而,如果安装了希腊或者俄语版的Windows NT,您将发现情况就大不一样了。例如,如果安装了希腊版的Windows NT,则美国英语、德语、希腊语和俄语键盘将会产生与美国英语版Windows NT同样的Unicode字符代码。不过,希腊版的Windows NT将不显示德语重音字符或者俄语字符,因为这些字符并不在希腊系统字体中。同样,俄语版的Windows NT也不显示德语重音字符或者希腊字符,因为这些字符也不在俄语系统字体中。

其中,Unicode版的KEYVIEW1的区别在日语版Windows NT下更具戏剧性。您从IME输入日文字符,这些字符可以正确显示。唯一的问题是格式:因为日文字符通常看起来非常复杂,它们的显示宽度是其它字符的两倍。

TrueType 和大字体

我们使用的点阵字体(在日文版Windows中带有附加字体)最多包括256个字符。这是我们所希望的,因为当假定字符代码是8位时,点阵字体文件的格式就跟早期Windows时代的样子一样了。这就是为什么当我们使用SYSTEM_FONT或者SYSTEM_FIXED_FONT时,某些语言中一些字符总不能正确显示(日本系统字体有点不同,因为它是双字节字符集;大多数字符实际上保存在TrueType集合文件中,文件扩展名是.TTC)。

TrueType字体包含的字符可以多于256个。并不是所有TrueType字体中的字符都多于256个,但Windows 98和Windows NT中的字体包含多于256个字符。或者,安装了多语系支持后,TrueType字体中也包含多于256个字符。在「 控制台」的「新增 /删除程序」中,单击「Windows 安装程序」页面卷标,并确保选中了「 多语系支持」。这个多语系支持包括五个字符集:波罗的海语系、中欧语系、斯拉夫语系、希腊语系和土耳其语系。波罗的海语系字符集用于爱沙尼亚语、拉脱维亚语和立陶宛语。中欧字符集用于阿尔巴尼亚语、捷克语、克罗地亚语、匈牙利语、波兰语、罗马尼亚语、斯洛伐克语和斯洛文尼亚语。斯拉夫字符集用于保加利亚语、白俄罗斯语、俄语、塞尔维亚语和乌克兰语。

Windows 98中的TrueType字体支持这五种字符集,再加上西欧(ANSI)字符集,西欧字符集实际上用于其它所有语言,但远东语言(汉语、日语和朝鲜语)除外。支持多种字符集的TrueType字体有时也称为「大字体」。在这种情况下的「大」并不是指字符的大小,而是指数量。

即使在非Unicode程序中也可利用大字体,这意味着可以用大字体显示几种不同字母表中的字符。然而,为了要将得到的字体选进设备内容,还需要GetStockObject以外的函数。

函数CreateFont和CreateFontIndirect建立了一种逻辑字体,这与CreatePen建立逻辑画笔以及CreateBrush建立逻辑画刷的方式类似。CreateFont用14个参数描述要建立的字体。CreateFontIndirect只有一个参数,但该参数是指向LOGFONT结构的指针。LOGFONT结构有14个字段,分别对应于CreateFont函数的参数。我将在第十七章详细讨论这些函数。现在,让我们看一下CreateFont函数,但我们只注意其中两个参数,其它参数都设定为0。

如果需要等宽字体(就像KEYVIEW1程序中使用的),将CreateFont的第13个参数设定为FIXED_PITCH。如果需要非内定字符集的字体(这也是我们所需要的),将CreateFont的第9个参数设定为某个「字符集ID」。此字符集ID将是WINGDI.H中定义的下列值之一。我已给出注释,指出和这些字符集相关的代码页:

#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, 日本)

#define HANGEUL_CHARSET

129

// 949 (DBCS, 韩文)

#define HANGUL_CHARSET

129

// " "

#define JOHAB_CHARSET

130

// 1361 (DBCS, 韩文)

#define GB2312_CHARSET

134

// 936 (DBCS, 简体中文)

#define CHINESEBIG5_CHARSET

136

// 950 (DBCS, 繁体中文)

#define GREEK_CHARSET

161

// 1253希腊文

#define TURKISH_CHARSET

162

// 1254 Latin 5 (土耳其文)

#define VIETNAMESE_CHARSET

163

// 1258越南文

#define HEBREW_CHARSET

177

// 1255希伯来文

#define ARABIC_CHARSET

178

// 1256阿拉伯文

#define BALTIC_CHARSET

186

// 1257波罗的海字集

#define RUSSIAN_CHARSET

204

// 1251俄文 (斯拉夫语系)

#define THAI_CHARSET

222

// 874泰文

#define EASTEUROPE_CHARSET

238

// 1250 Latin 2 (中欧语系)

#define OEM_CHARSET

255

// 地区自订

为什么Windows对同一个字符集有两个不同的ID:字符集ID和代码页ID?这只是Windows中的一种怪癖。注意,字符集ID只需要1字节的储存空间,这是LOGFONT结构中字符集字段的大小(试回忆Windows 1.0时期,内存和储存空间有限,每个字节都必须斤斤计较)。注意,有许多不同的MS-DOS代码页用于其它国家,但只有一种字符集ID-OEM_CHARSET-用于MS-DOS字符集。

您还会注意到,这些字符集的值与STOKFONT程序最上头的「CharSet」值一致。在美国英语版Windows中,我们看到常备字体的字符集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。

程序6-4所示的KEYVIEW2程序实作了键盘布局改变时改变字体的逻辑。

程序6-4 KEYVIEW2
        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) ;        }        

注意,键盘输入语言改变后,KEYVIEW2就清除画面并重新分配储存空间。这样做有两个原因:第一,因为KEYVIEW2并不是某种字体专用的,当输入语言改变时字体文字的大小也会改变。程序需要根据新字符大小重新计算某些变量。第二,在接收每个字符消息时,KEYVIEW2并不有效地保留字符集ID。因此,如果键盘输入语言改变了,而且KEYVIEW2需要重画显示区域时,所有的字符将用新字体显示。

第十七章将详细讨论字体和字符集。如果您想深入研究国际化问题,可以在/Platform SDK/Windows Base Services/International Features找到需要的文件,还有许多基础信息则位于/Platform SDK/Windows Base Services/General Library/String Manipulation。

插入符号(不是光标)

当您往程序中输入文字时,通常有一个底线、竖条或者方框来指示输入的下一个字符将出现在屏幕上的位置。这个标志通常称为「光标」,但是在Windows下写程序,您必须改变这个习惯。在Windows中,它称为「插入符号」。「光标」是指表示鼠标位置的那个位图图像。

原创粉丝点击