[翻译]-WinCE 程序设计 (3rd 版)--2.2 输出文本

输出文本

在第一章里,例子程序HelloCE调用DrawText函数显示了一行文本。代码如下:
DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect,
          DT_CENTER | DT_VCENTER | DT_SINGLELINE);
DrawText是一个相当高级的函数,允许由程序显示文本,而由Windows处理大部分细节。DrawText的头几个参数几乎是不言而喻,很直观。当前正在使用的设备环境句柄被传入,同时传入的还有被TEXT宏包围的用来显示的文本,声明成Unicode字符串是为了符合Windows CE的需要。

第三个参数是要输出的字符个数,当为-1,则表示传入的是以NULL为终止符的字符串并由Windows计算其长度。

第四个参数是一个指向rect结构的指针,为文本规定了格式化矩形。DrawText用该矩形作为文本格式化输出的基础。文本如何格式化取决于函数的最后一个参数--格式化标志位。这些标志位指定文本如何被放在格式化的矩形里。在指定了DT_CALCRECT标志位的情况下,由DrawText来计算需要输出的文本的尺寸。DrawText甚至用自动计算出的行中断(line break)来将文本格式化多行。在HelloCE的情况里,标志位规定文本应该水平居中(DT_CENTER)和垂直居中(DT_VCENTER)。DT_VCENTER标志只在单行文本的情况下有效,所以最后一个标志位DT_SINGLELINE规定如果矩形宽度不足以显示整个字符串时,文本不应该折成多行。

画文本的另一个方法就是使用下面的函数:
BOOL ExtTextOut (HDC hdc, int X, int Y, UINT fuOptions,
                 const RECT *lprc, LPCTSTR lpString,
                 UINT cbCount, const int *lpDx);
ExtTextOut函数同DrawText相比有一些优势。首先,ExtTextOut画单行文本往往更快一些。其次,文本并不在矩形里格式化,而是以传入的x、y坐标作为文本绘制的起始坐标。通常,该点是矩形的左上角坐标,但它可以随着DC中文本对齐方式来改变。传入的rect参数用做剪切矩形,如果背景模式是opaque,则背景颜色的区域被绘制。该矩形参数可以是NULL,表示不需要剪切或者opaquing(不透明化)。接下来的两个参数是文本及字符个数。最后一个参数允许应用程序指定相邻字符之间的水平距离。

Windows CE与其它版本的Windows不同的地方在于只有这两个文本绘制函数用于显示文本。通过使用DrawText或ExTextOut,您可以模拟TextOut 和 TabbedTextOut等其它版本Windows里文本函数所能做的大部分操作。这是Windows CE同Windows早期版本不同的地方之一,通过牺牲向后兼容性来获得一个更小的操作系统。

设备环境属性
关于HelloCe中用的DrawText,我还没有提到的是当显示文本时程序对DC配置做的大量假设。在Windows设备环境上绘制需要很多参数,例如前景色和背景色、文字如何绘制在背景上以及文字的字体等。每次绘制时并不是指定所有这些参数,设备环境保持当前设置,也就是属性,每次在绘制设备变量上绘制时都使用这些属性。

前景色和背景色
文本属性最显而易见的是前景色和背景色。SetTextColor 和GetTextColor允许程序设置和获取当前颜色。这两个函数在Windwos CE设备支持的灰度级屏幕和彩色屏幕上都可以运行的很好。

使用前面提到的GetDeviceCaps函数,可以确定设备支持多少颜色。该函数原型如下:
int GetDeviceCaps (HDC hdc, int nIndex);
您需要被查询的DC的句柄,因为不同的DC有不同的内容。例如,打印机DC就不同于显示器DC。第二个参数指出要查询的内容。在返回设备上可用颜色时,当设备支持256色或更少颜色时,NUMCOLORS返回支持的颜色数量。当超过256时,NUMCOLORS对应的返回值为-1,用BITSPIXEL可以返回颜色,此时返回的是每个像素所用的位(bit)数。通过将BITSPIXEL的返回值左移动一位,可以将这个值转换为颜色数,代码示例如下:
nNumColors = GetDeviceCaps (hdc, NUMCOLORS);
if (nNumColors == -1)
    nNumColors = 1 << GetDeviceCaps (hdc, BITSPIXEL);

文字对齐方式
当用ExtTextOut显示文本时,系统用DC的文字对齐方式来决定在哪里绘制文本。通过SetTextAlign 函数,文字可以水平和垂直对齐。

SetTextAlign 如下:
UINT WINAPI SetTextAlign (HDC hdc, INT fmode);
传给fmode的对齐标方式标志位如下所示:
TA_LEFT 文本左边缘与控制点对齐(控制点解释见后)
TA_RIGHT文本右边缘与控制点对齐
TA_TOP文本顶部与控制点对齐
TA_CENTER文本以控制点为中心水平居中
TA_BOTTOM文本底部边缘与控制点对齐
TA_BASELINE文本基线与控制点对齐
TA_NOUPDATECP在调用ExtTextOut后,DC的当前点并不更新
TA_UPDATECP在调用ExtTextOut后,DC的当前点更新
上面描述中提到的控制点指的是传给ExtTextOut的x、y坐标。对SetTextAlign的每次调用,垂直对齐标志和水平对齐标志可以组合在一起。

因为很难形象化的描述这些标志位的效果,图2-1展示了每个标志的效果。在图中,X表示控制点。

图2-1(略):当前绘制点和文本对齐标志之间的关系

绘制模式
影响文本输出的另一个属性是背景模式。当字母在设备环境上绘制时,系统使用前景色绘制字母自身。字母之间的空间则是另一回事。如果背景模式设置为不透明,则使用当前背景色来绘制该空间。但如果背景模式设置为透明,字母之间的空间保持文字绘制之前的状态。虽然这可能不像是一个大的区别,但设想一个使用图画或者图片来填充的窗口背景。如果文字在图片顶部输出,背景模式设置为不透明,文字周围的区域会被填充,背景色会填充在图片上。如果背景模式是透明,文字在图片上看起来就像文字原本就在图片上一样,图片会在文本的字母之间显示出来。

TextDemo示例程序
TextDemo程序,演示了文本颜色、背景色和背景模式之间的关系。程序如列表2-1所示:
列表2-1:TextDemo程序
TextDemo.h
//================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
  
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
  
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT {                             // Structure associates
    UINT Code;                                  // messages
                                                // with a function.
    LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD {                              // Structure associates
    UINT Code;                                  // menu IDs with a
    LRESULT (*Fxn)(HWND, WORD, HWND, WORD);     // function.
};
  
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
  
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
  
// Message handlers
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);

TextDemo.cpp
//================================================================
// TextDemo - Text output demo
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h>                 // For all that Windows stuff
#include "TextDemo.h"                // Program-specific stuff
  
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("TextDemo");
HINSTANCE hInst;                     // Program instance handle
  
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_PAINT, DoPaintMain,
    WM_DESTROY, DoDestroyMain,
};
//======================================================================
// Program Entry Point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    MSG msg;
    int rc = 0;
    HWND hwndMain;
  
    // Initialize this instance.
    hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
    if (hwndMain == 0)
        return 0x10;
  
     // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
    WNDCLASS wc;
    HWND hWnd;
  
    hInst = hInstance;   // Save handle in global variable.
  
#if defined(WIN32_PLATFORM_PSPC)
    // If Pocket PC, allow only one instance of the application.
    hWnd = FindWindow (szAppName, NULL);
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));   
        return 0;
    }
#endif   
    // Register application main window class.
    wc.style = 0;                             // Window style
    wc.lpfnWndProc = MainWndProc;             // Callback function
    wc.cbClsExtra = 0;                        // Extra class data
    wc.cbWndExtra = 0;                        // Extra window data
    wc.hInstance = hInstance;                 // Owner handle
    wc.hIcon = NULL,                          // Application icon
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
    wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName =  NULL;                  // Menu name
    wc.lpszClassName = szAppName;             // Window class name
  
    if (RegisterClass (&wc) == 0) return 0;
  
    // Create main window.
    hWnd = CreateWindowEx (WS_EX_NODRAG,      // Ex Style flags
                         szAppName,           // Window class
                         TEXT("TextDemo"),    // Window title
                         // Style flags
                         WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
                         CW_USEDEFAULT,       // x position
                         CW_USEDEFAULT,       // y position
                         CW_USEDEFAULT,       // Initial width
                         CW_USEDEFAULT,       // Initial height
                         NULL,                // Parent
                         NULL,                // Menu, must be null
                         hInstance,           // Application instance
                         NULL);               // Pointer to create
                                              // Parameters
    // Return fail code if window not created.
    if ((!hWnd) || (!IsWindow (hWnd))) return 0;
  
    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
    return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam) {
    INT i;
    //
    // Search message list to see if we need to handle this
    // message.  If in list, call procedure.
    //
    for (i = 0; i < dim(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                     LPARAM lParam) {
    PAINTSTRUCT ps;
    RECT rect, rectCli;
    HBRUSH hbrOld;
    HDC hdc;
    INT i, cy;
    DWORD dwColorTable[] = {0x00000000, 0x00808080,
                            0x00cccccc, 0x00ffffff};
  
    GetClientRect (hWnd, &rectCli);
  
    hdc = BeginPaint (hWnd, &ps);
  
    // Get the height and length of the string.
    DrawText (hdc, TEXT ("Hello Windows CE"), -1, &rect,
              DT_CALCRECT | DT_CENTER | DT_SINGLELINE);
  
    cy = rect.bottom - rect.top + 5;
  
    // Draw black rectangle on right half of window.
    hbrOld = (HBRUSH)SelectObject (hdc, GetStockObject (BLACK_BRUSH));
    Rectangle (hdc, rectCli.left + (rectCli.right - rectCli.left) / 2,
               rectCli.top, rectCli.right, rectCli.bottom);
    SelectObject (hdc, hbrOld);
  
    rectCli.bottom = rectCli.top + cy;
    SetBkMode (hdc, TRANSPARENT);
    for (i = 0; i < 4; i++) {
        SetTextColor (hdc, dwColorTable[i]);
        SetBkColor (hdc, dwColorTable[3-i]);
  
        DrawText (hdc, TEXT ("Hello Windows CE"), -1, &rectCli,
                  DT_CENTER | DT_SINGLELINE);
        rectCli.top += cy;
        rectCli.bottom += cy;
    }
  
    SetBkMode (hdc, OPAQUE);
    for (i = 0; i < 4; i++) {
        SetTextColor (hdc, dwColorTable[i]);
        SetBkColor (hdc, dwColorTable[3-i]);
  
        DrawText (hdc, TEXT ("Hello Windows CE"), -1, &rectCli,
                  DT_CENTER | DT_SINGLELINE);
        rectCli.top += cy;
        rectCli.bottom += cy;
    }
    EndPaint (hWnd, &ps);
    return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                       LPARAM lParam) {
    PostQuitMessage (0);
    return 0;
}
TextDemo的实质内容在OnPaintMain函数里。对DrawText的第一次调用并没有在设备环境上画任何东西。相反,使用DT_CALCRECT标志指示Windows将文本串的矩形尺寸存在rect里。该信息用于计算字符串的高度,并存储在cy里。接下来,在窗口的右侧绘制了一个黑色矩形。我将在本章稍后一些讨论如何绘制一个矩形。该矩形用来在文本被书写之前,产生两个不同的背景。函数接下来用不同的前景色、背景色在透明和不透明模式下输出了 同样的字符串。程序结果如图2-2所示:

头四行使用透明模式来绘制。后四行使用不透明模式来绘制。文本颜色从黑到白,每行使用不同的颜色,同时背景色设置为从白到黑。在透明模式下,背景色是无关紧要的,因为并不使用该背景色。但是在不通明模式下,背景色欣然出现在每行里。
图2-2(略):TextDemo展示了文本色、背景色以及背景模式之间的关系。

字体
如果Windows提供的全部灵活性就是设置前景色和背景色,那我们还是回到MS-DOS和字符属性时代的好。有证据表明,Windows同MS-DOS最显著的变化就是Windows可改变显示文本字体的能力。所有Windows操作系统都是建立在WYS/WYG-所见即所得-的概念上的,而采用可变字体就是达到这一目的的一个主要手段。

在所有Windows操作系统里都有两种字体类型-光栅型(raster)和TrueType型。光栅型字体存储为位图,即小的像素图像,每个字符都有一个。光栅字体容易存储和使用,但有一个重要问题:不能很好的缩放。就像小图放大时出现锯齿纹一样,光栅字体放大到更大的字体时会出现锯齿纹。

TrueType字体解决了缩放问题。它不是存储成图象,每个TrueType字符存都存储成如何绘制字符的描述信息。作为在屏幕上绘制字符的Windows功能的一部分,字体引擎获得描述信息,并按需要的尺寸在屏幕上绘制字符。一个Windows CE系统支持TrueType或光栅字体,但不会同时支持。幸运的是,对光栅字体和TrueType字体来说,编程接口都是一样的,这减少了Windows 开发者对字体技术的担忧,毕竟字体技术用在所有应用里,也是最让人吃力的技术之一。

Windows CE里的字体函数同Windows其它版本中的字体函数很相近。从创建字体、选择进DC到最后删除字体,让我们看一下字体生存周期中用到的函数吧。如何查询当前字体以及枚举可使用的字体等,都会在下面几节里涉及到。

创建字体
在应用可以使用非默认字体之前,该字体必须被创建并被选进设备环境里。在新字体被选进DC后,在DC里绘制的任何文本都使用这个新字体。
在Windows CE中创建字体的方法如下:
HFONT CreateFontIndirect (const LOGFONT *lplf);
该函数接受一个指向LOGFONT结构的指针,该结构必须使用您需要的字体描述来填充。
typedef struct tagLOGFONT {
    LONG lfHeight;
    LONG lfWidth;
    LONG lfEscapement;
    LONG lfOrientation;
    LONG lfWeight;
    BYTE lfItalic;
    BYTE lfUnderline;
    BYTE lfStrikeOut;
    BYTE lfCharSet;
    BYTE lfOutPrecision;
    BYTE lfClipPrecision;
    BYTE lfQuality;
    BYTE lfPitchAndFamily;
    TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;

lfHeight 规定字体在设备单位下的高度。如果该域为0,字体管理器返回使用的字体系中默认字体尺寸。对于大多数应用程序来说,您需要创建一个特殊尺寸的字体。下面的公式用于给lfHeight转换磅值:
lfHeight = –1 * (PointSize * GetDeviceCaps (hdc, LOGPIXELSY) / 72);
这里传给GetDeviceCaps的LOGPIXELSY告诉函数返回垂直方向上每英寸的逻辑像素数。72表示每英寸的磅值(磅是用于排版的计算单位)

lfWidth规定了平均字符宽度。因为字体的高度比宽度更重要,所以大多数程序都把这个值设为0。表示让字体管理器根据字体高度计算出合适的宽度。lfEscapement 和lfOrientation 规定了字符基线同X坐标轴之间以十分之一度为单位的旋转角度。lfWeight规定了从0到1000范围内的字体粗细程度,其中400是标准字体,700是加重字体。接下来的三个域规定了字体是否是斜体、下划线或者删除线。

lpCharSet规定了您选择的字符集。该域对于软件的国际版本是很重要的,因为国际版本一般都要求特定的语言字符集合。lfOutPrecision 规定Windows匹配您请求的字体的精确程度。在众多可以使用的标志当中,OUT_TT_ONLY_PRECIS标志规定创建的字体必须是TrueType字体。

lfClipPrecision规定Windows如何裁剪超出显示区域的字符。

lfQuality 可以设置为以下几种:
DEFAULT_QUALITY   默认系统质量

DRAFT_QUALITY   牺牲质量换取速度

CLEARTYPE_QUALITY   用ClearType技术绘制文本

CLEARTYPE_COMPAT_QUALITY   用ClearType技术绘制文本。对非ClearType字体使用同样的空间。

ClearType是一种为字体提供更清晰的外观的文本显示技术,该技术独立寻址红、绿、蓝LCD,它们在彩色LCD显示器上构成一个像素。根据系统的不同,有的可能不支持ClearType,有的可能对系统里的所有字体都支持。对于支持ClearType但不完全支持的系统来说,使用CLEARTYPE_QUALITY 或CLEARTYPE_COMPAT_QUALITY 可以创建用ClearType绘制的字体。因为ClearType并不改进所有字体的外观,所以您应该测试一下,看ClearType是否对您选择的字体有所改进。

lfPitchAndFamily规定了您选择的字体的字系。当您请求诸如Swiss字系--一种没有衬线的专业字体--时,使用该域很方便。再例如选择Roman字系--一种带衬线的专业字体--时,也很方便,但您要注意不要针对特定的字体。您也可以用该域来规定均衡或等宽字体,并让Windows来决定使用哪种字体来匹配传给LOGFONT结构的特定特性。最后,lfFaceName域用来指明具体字体的字体名称。当用一个填充后的LOGFONT结构调用CreateFontIndirect时,Windows根据提供的特性,创建一个最匹配的逻辑字体。为了使用该字体,需要做的最后一步是选择该字体到一个设备环境里。

选择字体到设备环境
用SelectObject 函数将字体选进DC里,函数如下所示:
HGDIOBJ SelectObject (HDC hdc, HGDIOBJ hgdiobj);
该函数不仅仅用于设置默认字体,还用在很多地方;正如您很快就会看到的,可以用该函数来选择其它GDI对象。该函数返回先前被选择的对象(在本例中返回的是先前选择的字体),应该保存该返回值以便当您用完新的字体后可以把先前的选择回DC里。代码行如下所示:
hOldFont = (HFONT)SelectObject (hdc, hFont);

当逻辑字体被选择时,系统从可使用的字体中选择最匹配的逻辑字体。对没有TrueType字体的设备,匹配的字体同指定的参数相比有一定数量的差异。因此,不要想当然的认为您请求了一个特殊字体,返回的就是准确匹配的字体。例如,您要求的字体高度可能和选进设备环境的字体高度是不一样的。

查询字体特性
为了确定选进设备环境的字体的特性,调用GetTextMetrics 函数来返回字体特性,函数原型如下:
BOOL GetTextMetrics (HDC hdc, LPTEXTMETRIC lptm);
TEXTMETRIC结构带有返回信息,该结构定义如下:
typedef struct tagTEXTMETRIC {
    LONG tmHeight;
    LONG tmAscent;
    LONG tmDescent;
    LONG tmInternalLeading;
    LONG tmExternalLeading;
    LONG tmAveCharWidth;
    LONG tmMaxCharWidth;
    LONG tmWeight;
    LONG tmOverhang;
    LONG tmDigitizedAspectX;
    LONG tmDigitizedAspectY;
    char tmFirstChar;
    char tmLastChar;
    char tmDefaultChar;
    char tmBreakChar;
    BYTE tmItalic;
    BYTE tmUnderlined;
    BYTE tmStruckOut;
    BYTE tmPitchAndFamily;
    BYTE tmCharSet;
} TEXTMETRIC;

TEXTMETRIC结构包含了LOGFONT结构中的很多域,但此时TEXTMETRIC的值是选进设备变量的字体的特性。图2-3显示了一些域同实际字符之间的关系。
图2-3(图略):TEXTMETRIC结构及其与字体的关系。

除了可以判断您是否真的获得了您需要的字体外,GetTextmetrics函数调用还有另外一个有价值的用途--确定字体高度。回忆一下TextDemo程序,行的高度是通过调用DrawText函数计算出来的。虽然那种方法很方便,但它有点慢。您可以使用TEXTMETRIC数据,以更加直接的方法来计算高度。将表示字符高度的tmHeight域与tmExternalLeading域--表示一行底部像素到下一行顶部像素之间的间距--相加,您就可以获得两行文本基线的间距了。

虽然GetTextMetrics对确定字符高度来说是很好用的,但它只提供了字体的平均宽度和最大宽度。如果需要TrueType的更多细节,可以使用函数GetCharABCWidths,函数原型如下:
BOOL GetCharABCWidths (HDC hdc, UINT uFirstChar, UINT uLastChar, LPABC lpabc);
GetCharABCWidths返回由uFirstChar 和 uLastChar 参数所确定的一系列字符的ABC宽度。该函数检查hdc参数指定的DC里的字体。ABC结构如下:
typedef struct _ABC {
    int     abcA;
    UINT    abcB;
    int     abcC;
} ABC;
abcA为在放置字符轮廓前的空白间距,abcB为字符轮廓本身的间距,abcC为字符轮廓右方的空白间距。abcA和abcC都可以是负值,用来指示缩进或者凸起。

要获得点阵字体(bitmap fonts)的宽度,可以使用GetCharWidth32函数。对指定字符范围内的每个字符,该函数返回一个字符宽度数组。

销毁字体
同其它GDI资源一样,在程序用完后,字体必须被销毁。在终止程序前如果删除字体失败,将导致资源泄露--孤立的图形资源,占用了珍贵的内存但却不再被应用程序拥有。

为了销毁字体,首先将字体从所在的设备环境里取消选择,这可以通过调用SelectObject来完成。传入的字体是最初调用SelectObject来选择字体时返回的字体。字体取消选择后,调用DeleteObject从系统里删除字体。DeleteObject原型如下:
BOOL DeleteObject (HGDIOBJ hObject);
hObject 表示要删除的字体句柄。
从这个过程可以看出,字体管理不是一个小事情。LOGFONT结构的许多参数可能令人畏惧,但它们赋予了应用程序准确指定字体的巨大能力。

处理字体时的一个问题是判断在具体设备上能够支持什么类型的字体。Windows CE设备提供标准字体集合,但制造商或者用户可能在具体系统上装载了附加字体。幸运地是,Windows提供了枚举系统上所有字体的方法。

枚举字体
为了判断系统上有什么字体可以使用,Windows提供了以下函数:
int EnumFontFamilies (HDC hdc, LPCTSTR lpszFamily, FONTENUMPROC lpEnumFontFamProc, LPARAM lParam);
该函数列举所以字系以及字系里的每个字体。头一个参数是设备环境。第二个参数是需要枚举的字系的名字。如果该参数为NULL,表示枚举每个可以使用的字系。

第三个参数稍微有些不同,是一个指向由应用程序提供的函数的指针。该函数是一个回调函数,由Windows为每个枚举的字体调用。最后一个参数,lParam,是由应用程序使用的普通参数。该值不经修改,直接传给了应用程序回调过程。

虽然回调函数的名字可以随便起,但函数原型必须声明为:
int CALLBACK EnumFontFamProc (LOGFONT *lpelf, TEXTMETRIC *lpntm, DWORD FontType, LPARAM lParam);
传给回调函数的第一个参数是指向LOGFONT结构的指针,用来描述被枚举的字体。第2个参数,指向textmetric的指针,进一步描述该字体。字体类型参数指明字体是光栅字体还是TrueType字体。

FontList示例程序

FontList程序用两种方式使用EnumFontFamilies函数来枚举系统里所有字体

清单2-2

FontList.h
//================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT {                             // Structure associates
    UINT Code;                                  // messages
                                                // with a function.
    LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD {                              // Structure associates
    UINT Code;                                  // menu IDs with a
    LRESULT (*Fxn)(HWND, WORD, HWND, WORD);     // function.
};
//----------------------------------------------------------------------
// Program-specific structures
//
#define FAMILYMAX   24
typedef struct {
    int nNumFonts;
    TCHAR szFontFamily[LF_FACESIZE];
} FONTFAMSTRUCT;
typedef FONTFAMSTRUCT *PFONTFAMSTRUCT;
  
typedef struct {
    INT yCurrent;
    HDC hdc;
} PAINTFONTINFO;
typedef PAINTFONTINFO *PPAINTFONTINFO;
  
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
  
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
  
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);

FontList.cpp
//======================================================================
// FontList - Lists the available fonts in the system
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h>                 // For all that Windows stuff
#include "FontList.h"                // Program-specific stuff
  
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("FontList");
HINSTANCE hInst;                     // Program instance handle
FONTFAMSTRUCT ffs[FAMILYMAX];
INT sFamilyCnt = 0;
  
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_CREATE, DoCreateMain,
    WM_PAINT, DoPaintMain,
    WM_DESTROY, DoDestroyMain,
};
  
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    MSG msg;
    int rc = 0;
    HWND hwndMain;
  
    // Initialize this instance.
    hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
    if (hwndMain == 0)
        return 0x10;
    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc;
    HWND hWnd;
  
    // Save program instance handle in global variable.
    hInst = hInstance;
  
#if defined(WIN32_PLATFORM_PSPC)
    // If Pocket PC, allow only one instance of the application.
    hWnd = FindWindow (szAppName, NULL);
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));   
        return 0;
    }
#endif   
    // Register application main window class.
    wc.style = 0;                             // Window style
    wc.lpfnWndProc = MainWndProc;             // Callback function
    wc.cbClsExtra = 0;                        // Extra class data
    wc.cbWndExtra = 0;                        // Extra window data
    wc.hInstance = hInstance;                 // Owner handle
    wc.hIcon = NULL,                          // Application icon
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName =  NULL;                  // Menu name
    wc.lpszClassName = szAppName;             // Window class name
  
    if (RegisterClass (&wc) == 0) return 0;
  
    // Create main window.
    hWnd = CreateWindowEx (WS_EX_NODRAG,      // Ex style flags
                         szAppName,           // Window class
                         TEXT("Font Listing"),// Window title
                         // Style flags
                         WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
                         CW_USEDEFAULT,       // x position
                         CW_USEDEFAULT,       // y position
                         CW_USEDEFAULT,       // Initial width
                         CW_USEDEFAULT,       // Initial height
                         NULL,                // Parent
                         NULL,                // Menu, must be null
                         hInstance,           // Application instance
                         NULL);               // Pointer to create
                                              // parameters
    // Return fail code if window not created.
    if (!IsWindow (hWnd)) return 0;
  
    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
    return nDefRC;
}
//======================================================================
// Font callback functions
//
//----------------------------------------------------------------------
// FontFamilyCallback - Callback function that enumerates the font
// families
//
int CALLBACK FontFamilyCallback (CONST LOGFONT *lplf,
                                 CONST TEXTMETRIC *lpntm,
                                 DWORD nFontType, LPARAM lParam) {
    int rc = 1;
  
    // Stop enumeration if array filled.
    if (sFamilyCnt >= FAMILYMAX)
        return 0;
    // Copy face name of font.
    lstrcpy (ffs[sFamilyCnt++].szFontFamily, lplf->lfFaceName);
    return rc;
}
//----------------------------------------------------------------------
// EnumSingleFontFamily - Callback function that enumerates fonts
//
int CALLBACK EnumSingleFontFamily (CONST LOGFONT *lplf,
                                   CONST TEXTMETRIC *lpntm,
                                   DWORD nFontType, LPARAM lParam) {
    PFONTFAMSTRUCT pffs;
  
    pffs = (PFONTFAMSTRUCT) lParam;
    pffs->nNumFonts++;    // Increment count of fonts in family
    return 1;
}
  
//----------------------------------------------------------------
// PaintSingleFontFamily - Callback function that draws a font
//
int CALLBACK PaintSingleFontFamily (CONST LOGFONT *lplf,
                                    CONST TEXTMETRIC *lpntm,
                                    DWORD nFontType, LPARAM lParam) {
    PPAINTFONTINFO ppfi;
    TCHAR szOut[256];
    INT nFontHeight, nPointSize;
    HFONT hFont, hOldFont;
  
    ppfi = (PPAINTFONTINFO) lParam;  // Translate lParam into struct
                                     // pointer.
  
    // Create the font from the LOGFONT structure passed.
    hFont = CreateFontIndirect (lplf);
  
    // Select the font into the device context.
    hOldFont = (HFONT)SelectObject (ppfi->hdc, hFont);
  
    // Compute font size.
    nPointSize = (lplf->lfHeight * 72) /
                 GetDeviceCaps(ppfi->hdc,LOGPIXELSY);
  
    // Format string and paint on display.
    wsprintf (szOut, TEXT ("%s   Point:%d"), lplf->lfFaceName,
              nPointSize);
    ExtTextOut (ppfi->hdc, 25, ppfi->yCurrent, 0, NULL,
                szOut, lstrlen (szOut), NULL);
  
    // Compute the height of the default font.
    nFontHeight = lpntm->tmHeight + lpntm->tmExternalLeading;
    // Update new draw point.
    ppfi->yCurrent += nFontHeight;
    // Deselect font and delete.
    SelectObject (ppfi->hdc, hOldFont);
    DeleteObject (hFont);
    return 1;
}
//================================================================
// Message handling procedures for MainWindow
//
//----------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam) {
    INT i;
    //
    // Search message list to see if we need to handle this
    // message.  If in list, call procedure.
    //
    for (i = 0; i < dim(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                      LPARAM lParam) {
    HDC hdc;
    INT i, rc;
  
    //Enumerate the available fonts.
    hdc = GetDC (hWnd);
    rc = EnumFontFamilies ((HDC)hdc, (LPTSTR)NULL,
        FontFamilyCallback, 0);
  
    for (i = 0; i < sFamilyCnt; i++) {
        ffs[i].nNumFonts = 0;
        rc = EnumFontFamilies ((HDC)hdc, ffs[i].szFontFamily,
                               EnumSingleFontFamily,
                               (LPARAM)(PFONTFAMSTRUCT)&ffs[i]);
    }
    ReleaseDC (hWnd, hdc);
    return 0;
}
//---------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                     LPARAM lParam) {
    PAINTSTRUCT ps;
    RECT rect;
    HDC hdc;
    TEXTMETRIC tm;
    INT nFontHeight, i;
    TCHAR szOut[256];
    PAINTFONTINFO pfi;
  
    GetClientRect (hWnd, &rect);
  
    hdc = BeginPaint (hWnd, &ps);
  
    // Get the height of the default font.
    GetTextMetrics (hdc, &tm);
    nFontHeight = tm.tmHeight + tm.tmExternalLeading;
  
    // Initialize struct that is passed to enumerate function.
    pfi.yCurrent = rect.top;
    pfi.hdc = hdc;
    for (i = 0; i < sFamilyCnt; i++) {
  
        // Format output string, and paint font family name.
        wsprintf (szOut, TEXT("Family: %s   "),
                  ffs[i].szFontFamily);
        ExtTextOut (hdc, 5, pfi.yCurrent, 0, NULL,
                    szOut, lstrlen (szOut), NULL);
        pfi.yCurrent += nFontHeight;
  
        // Enumerate each family to draw a sample of that font.
        EnumFontFamilies ((HDC)hdc, ffs[i].szFontFamily,
                          PaintSingleFontFamily,
                          (LPARAM)&pfi);
    }
    EndPaint (hWnd, &ps);
    return 0;
}
//----------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                       LPARAM lParam) {
    PostQuitMessage (0);
    return 0;
}
当应用程序在OnCreateMain中处理WM_CREATE消息时,首先枚举不同的字体。此处调用EnumFontFamilies函数,其中FontFamily域设为NULL表示每个字系都将被枚举。在回调函数FontFamilyCallback里,字系的名字被复制进一个字符串数组里。


在处理WM_PAINT消息的过程里完成剩余的工作。OnPaint函数以惯常的标准方式开头,依然是先获取客户区域大小并调用BeginPaint函数,该函数返回窗口的设备环境句柄。接下来调用GetTextMetrics来计算默认字体的行高。之后进入循环,为OnCreateMain中枚举出的每个字系调用EnumFontFamilies函数。用于这个回调序列的回调过程是迄今为止比较复杂的代码。

用于枚举单个字体的回调过程PaintSingleFontFamily使用lParam参数来获得一个指向PAINTFONTINFO结构的指针。该结构包含当前垂直绘制坐标以及设备环境的句柄。通过使用lParam指针,FontList可以避免声明全局变量来和回调过程通信。

接下来回调过程使用传进来的LOGFONT指针创建字体。新字体随后被选进设备环境,之前被选择的字体句柄被保存在hOldFont中。使用本章前面提到的转换公式来计算被枚举的字体的磅值。接下来输出一行文本,显示字系名称以及该字体的磅值。没有使用DrawText,回调过程用的是ExtTextOut来绘制字符串的。

显示完文本后,函数通过使用传入的TEXTMETRIC结构里的tmHeight和tmExternalLeading域来计算刚绘制的文本的行高。之后用最初选择的字体句柄调用SelectObject来取消对新字体的选择。随后用DeleteObject来删除新字体。最后,回调函数返回一个非零值来通知Windows一起正常,可以开始另一次枚举回调了。图2-4显示了字体列表窗口。注意字体名字体名称,每个字体有特定的大小集合。

图2-4(略):字体列表窗口显示了手持PC上一些可以使用的字体。

未完的事情

如果你仔细看图2-4,你会注意到一个显示上的问题。字体列表显示在了Font列表窗口的底边上。解决方法是为窗口增加滚动条。因为我将在第四章详细描述窗口控件,其中就包括滚动条,所以我暂时不讲述如何实现该方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值