简单的文字输出
让我们先来看看Windows为文字输出、影响文字的设备内容属性以及备用字体提供的各种函数。
文字输出函数
我已经在许多范例程序中使用过最常用的文字输出函数:
TextOut (hdc, xStart, yStart, pString, iCount) ;
参数xStart和yStart是逻辑坐标上字符串的起始点。通常,这是Windows开始绘制的第一个字母的左上角。TextOut需要指向字符串的指针和字符串的长度,这个函数不能识别以NULL终止的字符串。
TextOut函数的xStart和yStart参数的含义可由SetTextAlign函数改变。TA_LEFT、TA_RIGHT和TA_CENTER旗标影响使用xStart在水平方向上定位字符串的方式。默认值是TA_LEFT。如果在SetTextAlign函数中指定了TA_RIGHT,则后面的TextOut呼叫会将字符串的最后一个字符定位于xStart,如果指定了TA_CENTER,则字符串的中心位于xStart。
类似地,TA_TOP、TA_BOTTOM和TA_BASELINE旗标影响字符串的垂直位置。TA_TOP是默认值,它意味着字符串的字母顶端位于yStart,使用TA_BOTTOM意味着字符串位于yStart之上。可以使用TA_BASELINE定位字符串,使基准线位于yStart。基准线是如小写字母p、q、y等字母下部的线。
如果您使用TA_UPDATECP旗标呼叫SetTextAlign,Windows就会忽略TextOut的xStart和yStart参数,而使用由MoveToEx、LineTo或更改目前位置的另一个函数设定的位置。TA_UPDATECP旗标也使TextOut函数将目前位置更新为字符串的结尾(TA_LEFT)或字符串的开头(TA_RIGHT)。这在使用多个TextOut呼叫显示一行文字时非常有用。当水平位置是TA_CENTER时,在TextOut呼叫后,目前位置不变。
您应该还记得,第四章的一系列SYSMETS程序显示几列文字时,对每一列都需要呼叫一个TextOut,其替代函数是TabbedTextOut函数:
TabbedTextOut ( hdc, xStart, yStart, pString, iCount, iNumTabs, piTabStops, xTabOrigin) ;
如果文字字符串中含有嵌入的制表符(‘\t’或0x09),则TabbedTextOut会根据传递给它的整数数组将制表符扩展为空格。
TabbedTextOut的前五个参数与TextOut相同,第六个参数是跳位间隔数,第七个是以图素为单位的跳位间隔数组。例如,如果平均字符宽度是8个图素,而您希望每5个字符加一个跳位间隔,则这个数组将包含40、80、120,按递增顺序依此类推。
如果第六个和第七个参数是0或NULL,则跳位间隔按每八个平均字符宽度设定。如果第六个参数是1,则第七个参数指向一个整数,表示跳位间隔重复增大的倍数(例如,如果第六个参数是1,并且第七个参数指向值为30的变量,则跳位间隔设定在30、60、90…图素处)。最后一个参数给出了从跳位间隔开始测量的逻辑x坐标,它与字符串的起始位置可能相同也可能不同。
另一个进阶的文字输出函数是ExtTextOut(前缀Ext表示它是扩展的):
ExtTextOut (hdc, xStart, yStart, iOptions, &rect, pString, iCount, pxDistance) ;
第五个参数是指向矩形结构的指针,在iOptions设定为ETO_CLIPPED时,该结构为剪裁矩形,在iOptions设定为ETO_OPAQUE时,该结构为用目前背景色填充的背景矩形。这两种选择您可以都采用,也可以都不采用。
最后一个参数是整数数组,它指定了字符串中连续字符的间隔。程序可以使用它使字符间距变窄或变宽,因为有时需要在较窄的列中调整单个文字。该参数可以设定为NULL来使用内定的字符间距。
用于写文字的高级函数是DrawText,我们第一次遇到它是在第三章讨论HELLOWIN程序时,它不指定坐标的起始位置,而是通过RECT结构型态定义希望显示文字的区域:
DrawText (hdc, pString, iCount, &rect, iFormat) ;
和其它文字输出函数一样,DrawText需要指向字符串的指针和字符串的长度。然而,如果在DrawText中使用以NULL结尾的字符串,就可以将iCount设定为-1,Windows会自动计算字符串的长度。
当iFormat设定为0时,Windows会将文字解释为一系列由carriage return字符(‘\r’或0x0D)或linefeed字符(‘\n’或0x0A)分隔的行。文字从矩形的左上角开始,carriage return字符或linefeed字符被解释为换行字符,因此Windows会结束目前行而开始新的一行。新的一行从矩形的左侧开始,在上一行的下面空开一个字符的高度(没有外部间隔)。包含字母的任何文字都应该显示在所剪裁矩形底部的右边或下边。
您可以使用iFormat参数更改DrawText的内定操作,iFormat由一个或多个旗标组成。DT_LEFT旗标(默认值)指定了左对齐的行,DT_RIGHT指定了向右对齐的行,而DT_CENTER指定了位于矩形左边和右边中间的行。因为DT_LEFT的值是0,所以如果只需要左对齐,就不需要包含标识符。
如果您不希望将carriage return字符或linefeed字符解释为换行字符,则可以包括标识符DT_SINGLELINE。然后,Windows会把carriage return字符和linefeed字符解释为可显示的字符,而不是控制字符。在使用DT_SINGLELINE时,还可以将行指定为位于矩形的顶端(DT_TOP)、底端(DT_BOTTOM)或者中间(DT_VCETER,V表示垂直)。
在显示多行文字时,Windows通常只在carriage return字符或linefeed字符处换行。然而,如果行的长度超出了矩形的宽度,则可以使用DT_WORDBREAK旗标,它使Windows在行内字的末尾换行。对于单行或多行文字的显示,Windows会把超出矩形的文字部分截去,可以使用DT_NOCLIP跳过这个操作,这个旗标还加快了函数的速度。当Windows确定多行文字的行距时,它通常使用不带外部间距的字符高度,如果您想在行距中加入外部间距,就可以使用旗标DT_EXTERNALLEADING。
如果文字中包含制表符(‘\t’或0x09),则您需要包括旗标DT_EXPANDTABS。在内定情况下,跳位间隔设定于每八个字符的位置。通过使用旗标DT_TABSTOP,您可以指定不同的跳位间隔,在这种情况下,iFormat的高字节包含了每个新跳位间隔的字符位置数值。不过我建议您避免使用DT_TABSTOP,因为iFormat的高字节也用于其它旗标。
DT_TABSTOP旗标存在的问题,可以由新的函数DrawTextEx来解决,它含有一个额外的参数:
DrawTextEx (hdc, pString, iCount, &rect, iFormat, &drawtextparams) ;
最后一个参数是指向DRAWTEXTPARAMS结构的指针,它的定义如下:
typedef struct tagDRAWTEXTPARAMS { UINT cbSize ; // size of structure int iTabLength ; // size of each tab stop int iLeftMargin ; // left margin int iRightMargin ; // right margin UINT uiLengthDrawn ; // receives number of characters processed } DRAWTEXTPARAMS, * LPDRAWTEXTPARAMS ;
中间的三个字段是以平均字符的增量为单位的。
文字的设备内容属性
除了上面讨论的SerTextAlign外,其它几个设备内容属性也对文字产生了影响。在内定的设备内容下,文字颜色是黑色,但您可以用下面的叙述进行更改:
SetTextColor (hdc, rgbColor) ;
使用画笔的颜色和画刷的颜色,Windows把rgbColor的值转换为纯色,您可以通过呼叫GetTextColor取得目前文字的颜色。
Windows在矩形的背景区域中显示文字,它可能根据背景模式的设定进行着色,也可能不这样做。您可以使用
SetBkMode (hdc, iMode) ;
更改背景模式,其中iMode的值为OPAQUE或TRANSPARENT。内定的背景模式为OPAQUE,它表示Windows使用背景颜色来填充矩形的背景。您可以使用
SetBkColor (hdc, rgbColor) ;
来改变背景颜色。rgbColor的值是转换为纯色的值。内定背景色是白色。
如果两行文字靠得太近,其中一个的背景矩形就会遮盖另一个的文字。由于这种原因,我通常希望内定的背景模式是TRANSPARENT。在背景模式为TRANSPARENT的情况下,Windows会忽略背景色,也不对矩形背景区域着色。Windows也使用背景模式和背景色对点和虚线之间的空隙及阴影刷中阴影间的区域着色,就像第五章所讨论的那样。
许多Windows程序将WHITE_BRUSH指定为Windows用于擦出窗口背景的画刷,画刷在窗口类别结构中指定。然而,您可能希望您程序的窗口背景与使用者在「控制台」中设定的系统颜色保持一致,在这种情况下,可以在WNDCLASS结构中指定背景颜色的这种方式:
wndclass.hbrBackground = COLOR_WINDOW + 1 ;
当您想要在显示区域书写文字时,可以使用目前系统颜色设定文字色和背景色:
SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ; SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;
完成这些以后,就可以使您的程序随系统颜色的更改而变化:
case WM_SYSCOLORCHANGE : InvalidateRect (hwnd, NULL, TRUE) ; break ;
另一个影响文字的设备内容属性是字符间距。它的默认值是0,表示Windows不在字符之间添加任何空间,但您可以使用以下函数插入空间:
SetTextCharacterExtra (hdc, iExtra) ;
参数iExtra是逻辑单位,Windows将其转换为最接近的图素,它可以是0。如果您将iExtra取为负值(希望将字符紧紧压在一起),Windows会接受这个数值的绝对值─也就是说,您不能使iExtra的值小于0。您可以通过呼叫GetTextCharacterExtra取得目前的字符间距,Windows在传回该值前会将图素间距转换为逻辑单位。
使用备用字体
当您呼叫TextOut、TabbedTextOut、ExtTextOut、DrawText或DrawTextEx书写文字时,Windows使用设备内容中目前选择的字体。字体定义了特定的字样和大小。以不同字体显示文字的最简单方法是使用Windows提供的备用字体,然而,它的范围是很有限的。
您可以呼叫下面的函数取得某种备用字体的句柄:
hFont = GetStockObject (iFont) ;
其中,iFont是几个标识符之一。然后,您就可以将该字体选入设备内容:
SelectObject (hdc, hFont) ;
这些您也可以只用一步完成:
SelectObject (hdc, GetStockObject (iFont)) ;
在内定的设备内容中选择的字体称为系统字体,能够由GetStockObject的SYSTEM_FONT参数识别。这是调和的ANSI字符集字体。在GetStockObject中指定SYSTEM_FIXED_FONT(我在本书的前面几个程序中应用过),可以获得等宽字体的句柄,这一字体与Windows 3.0以前的系统字体兼容。在您希望所有的字体都具有相同宽度时,这是很方便的。
备用字体OEM_FIXED_FONT也称为终端机字体,是Windows在MS-DOS命令提示窗口中使用的字体,它包括与原始IBM-PC扩展字符集兼容的字符集。Windows在窗口标题列、菜单和对话框的文字中使用DEFULT_GUI_FONT。
当您将新字体选入设备内容时,必须使用GetTextMetrics计算字符的高度和平均宽度。如果选择了调和字体,那么一定要注意,字符的平均宽度只是个平均值,某些字符会比它宽或比它窄。在本章的后面,您会了解到确定由不同宽度字符所组成的字符串总宽度的方法。
尽管GetStockObject确实提供了存取不同字体的最简单方式,但是您还不能充分控件Windows所提供的字体。不久,您会看到指定字体字样和大小的方法。
字体的背景
本章剩余的部分致力于处理不同的字体。但是在您接触这些特定程序代码前,对Windows使用字体的基本知识有一个深入的了解是很有好处的。
字体型态
Windows支持两大类字体,即所谓的「GDI字体」和「设备字体」。GDI字体储存在硬盘的文件中,而设备字体是输出设备本来就有的。例如,通常打印机都具有内建的设备字体集。
GDI字体有三种样式:点阵字体,笔划字体和TrueType字体。
点阵字体的每个字符都以位图图素图案的形式储存,每种点阵字体都有特定的纵横比和字符大小。Windows通过简单地复制图素的行或列就可以由GDI点阵字体产生更大的字符。然而,只能以整数倍放大字体,并且不能超过一定的限度。由于这种原因,GDI点阵字体又称为「不可缩放的」字体。它们不能随意地放大或缩小。点阵字体的主要优点是显示性能(显示速度很快)和可读性(因为是手工设计的,所以尽可能清晰)。
/*----------------------------------------- CHOSFONT.C -- ChooseFont Demo (c) Charles Petzold, 1998 -----------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("ChosFont") ; 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 = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("ChooseFont"), 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 CHOOSEFONT cf ; static int cyChar ; static LOGFONT lf ; static TCHAR szText[] = TEXT ("\x41\x42\x43\x44\x45 ") TEXT ("\x61\x62\x63\x64\x65 ") TEXT ("\xC0\xC1\xC2\xC3\xC4\xC5 ") TEXT ("\xE0\xE1\xE2\xE3\xE4\xE5 ") #ifdef UNICODE TEXT ("\x0390\x0391\x0392\x0393\x0394\x0395 ") TEXT ("\x03B0\x03B1\x03B2\x03B3\x03B4\x03B5 ") TEXT ("\x0410\x0411\x0412\x0413\x0414\x0415 ") TEXT ("\x0430\x0431\x0432\x0433\x0434\x0435 ") TEXT ("\x5000\x5001\x5002\x5003\x5004") #endif ; HDC hdc ; int y ; PAINTSTRUCT ps ; TCHAR szBuffer [64] ; TEXTMETRIC tm ; switch (message) { case WM_CREATE: // Get text height cyChar = HIWORD (GetDialogBaseUnits ()) ; // Initialize the LOGFONT structure GetObject (GetStockObject (SYSTEM_FONT), sizeof (lf), &lf) ; // Initialize the CHOOSEFONT structure cf.lStructSize = sizeof (CHOOSEFONT) ; cf.hwndOwner = hwnd ; cf.hDC = NULL ; cf.lpLogFont = &lf ; cf.iPointSize = 0 ; cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS ; cf.rgbColors = 0 ; cf.lCustData = 0 ; cf.lpfnHook = NULL ; cf.lpTemplateName = NULL ; cf.hInstance = NULL ; cf.lpszStyle = NULL ; cf.nFontType = 0 ; cf.nSizeMin = 0 ; cf.nSizeMax = 0 ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FONT: if (ChooseFont (&cf)) InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; } return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Display sample text using selected font SelectObject (hdc, CreateFontIndirect (&lf)) ; GetTextMetrics (hdc, &tm) ; SetTextColor (hdc, cf.rgbColors) ; TextOut (hdc, 0, y = tm.tmExternalLeading, szText, lstrlen (szText)) ; // Display LOGFONT structure fields using system font DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ; SetTextColor (hdc, 0) ; TextOut (hdc, 0, y += tm.tmHeight, szBuffer, wsprintf (szBuffer, TEXT ("lfHeight = %i"), lf.lfHeight)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfWidth = %i"), lf.lfWidth)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfEscapement = %i"), lf.lfEscapement)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfOrientation = %i"), lf.lfOrientation)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfWeight = %i"), lf.lfWeight)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfItalic = %i"), lf.lfItalic)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfUnderline = %i"), lf.lfUnderline)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfStrikeOut = %i"), lf.lfStrikeOut)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfCharSet = %i"), lf.lfCharSet)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfOutPrecision = %i"), lf.lfOutPrecision)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfClipPrecision = %i"), lf.lfClipPrecision)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfQuality = %i"), lf.lfQuality)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfPitchAndFamily = 0x%02X"), lf.lfPitchAndFamily)) ; TextOut (hdc, 0, y += cyChar, szBuffer, wsprintf (szBuffer, TEXT ("lfFaceName = %s"), lf.lfFaceName)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }