再上一篇:2.9 WM_DESTROY消息
上一篇:3.1 显示的时机
主页
下一篇:3.3 滚动条的应用
再下一篇:3.4 关于文本显示的更多内容
文章列表

3.2 简单的文本显示

《Windows 下的 C/C++高级编程》讲述Windows环境下调用Win32 API函数程序设计

TextOut函数是Windows下最常用的GDI函数,这一节就以一个该函数的例子来演示Windows下通常的文本显示。

3.2.1 窗口坐标

要显示文本,一个很基本的问题是在什么地方显示,这就需要通过指定坐标来确定。Windows中GDI绘图函数通常使用称为“逻辑坐标”的坐标体系,包括TextOut函数。关于“逻辑坐标”的概念在下一章详细讲解。这里只需知道,Windows定义了多种不同的“映射方式”,用以将GDI绘图函数指定的逻辑坐标转换为显示器的物理像素坐标。映射方式在设备描述表中定义,在默认情况下,映射方式是MM_TEXT,而在该映射方式下,逻辑单位与物理单位相同,都是相对于用户区左上角的像素数,X方向从左向右递增,Y方向从上向下递增,如图3-2所示。

图3-2 MM_TEXT坐标系

也就是说,在默认情况下的MM_TEXT坐标系与Windows在PAINTSTRUCT结构中定义无效矩形时使用的坐标系完全相同,程序不必考虑其中的区别,可以假想它们是一回事。但是,在其他映射方式下并非如此。

#第33页~ Windows下的C/C++高级编程

3.2.2 文本和背景的颜色

在缺省状态下,当用户使用TextOut向窗口中输出文本时,显示在当前背景下的文本为黑色。可以使用API函数GetTextColor和GetBkColor来获得当前文本和背景的颜色。这两个函数定义如下:

COLORREF GetTextColor(

HDC hdc // 显示设备句柄

);

COLORREF GetBkColor(

HDC hdc // 显示设备句柄

);

这两个函数的返回值均为COLORREF类型,其值用来指定颜色,它是32位的整型值。Windows允许以3种不同的方法来指定颜色。首先,也是最常用的方法是作为一个RGB(red,green, blue)值显示。在RGB值中,相应浓度的3种颜色混在一起以产生实际的颜色。第2种指定颜色的方法是作为逻辑调色板的索引。第 3种方法是作为与调色板相关的一个 RGB值。本章只讨论第一种方法。使用RGB方法定义,是在一个32位整数中分别保留红色(Red)、绿色(Green)和蓝色(Blue)的值,然后三色混合可得各种所需的颜色,其编码方式见表3-1。

表3-1 RGB编码方式表

字节 颜色

24~31位(高字节) 全部为零(8位)

16~23位 红色(Red),取值范围:0~255(8位)

8~15位 绿色(Green),取值范围:0~255(8位)

0~7位(低字节) 蓝色(Blue),取值范围:0~255(8位)

RGB值中每种颜色的范围都是0到255,0是最低的浓度,255是最高的浓度。例如,下列长整型值将产生最亮的红色。

00 255 00 00

尽管用户可以自由构造COLORREF的值,但Windows定义了能完成此工作的宏RGB(),以方便用户定义自己所需的颜色。其通用格式如下:

COLORREF RGB(

BYTE byRed, // 红色组分值

BYTE byGreen, // 绿色组分值

BYTE byBlue // 蓝色组分值

);

其中,byred、bygreen和byblue必须是0到255之间的一个值。因此使用RGB(0,0,255)可以产生绿色,使用RGB(255,255,255)可产生白色,使用RGB(0,0,0)可产生黑色。把这3种颜色以不同的浓度混合起来可产生其他的颜色。例如,RGB(0,100,100)可产生浅绿色。用户可以通过试验来决定哪种颜色最适合自己的应用程序。

可以使用API函数SetTextColor和SetBkColor来设定文本和背景的颜色。这两个函数定 #第34页~

第3章 文本显示义如下:

COLORREF SetTextColor(

HDC hdc, // 显示设备句柄

COLORREF crColor // 文本颜色

);

COLORREF SetBkColor(

HDC hdc, // 显示设备句柄

COLORREF crColor // 文本背景颜色

);

有一点需要提醒一下,这里的文本背景色与定义窗口类时设置的背景并不相同。窗口类中的背景是一个纯色或非纯色的刷子,Windows用来擦除用户区,并不是设备描述表结构的一部分。而这里的文本背景色是用来填充字符周围的矩形空间的。

图3-3 字符背景色

不过,在定义窗口类结构时,大多数Windows应用程序使用WHITE_BRUSH,以使默认设备描述表中的默认文本背景色与Windows用以擦除用户区背景的刷子颜色相同。3.2.3 设置背景显示模式

通过使用SetBkMode函数,可以控制在屏幕上显示文本对背景的影响,该函数的定义如下:

int SetBkMode(

HDC hdc, // 显示设备句柄

int iBkMode // 背景模式

);

该函数决定显示文本(或其他类型的输出)时当前的背景颜色所受的影响。在hdc中指定受影响设备描述表的句柄。在mode中指定背景模式,其取值定义见表3-2。

表3-2 背景模式取值定义表

取值 描述

OPAQUE 每次输出文本时,窗口的背景色变成当前文本背景色

TRANSPARENT 背景不受影响,保持背景色不变

在缺省的情况下,背景模式为OPAQUE。

3.2.4 选择字体

字体定义了特定的字样和大小。当应用程序调用TextOut、DrawText等函数显示文本的

#第35页~ Windows下的C/C++高级编程

时候,Windows系统会使用设备描述表中当前选定的字体。应用程序可以使用不同的字体,程序员甚至可以自己定义字体并使用。Windows已经定义了一些常用的字体,供应用程序选用,见表3-3。对大多数应用程序来说,这些字体已经足够了。

表3-3 Windows提供的常用字体表

字体 说明

DEFAULT_GUI_FONT 标题栏、对话框等图形界面对象所使用的缺省字体。通常使用MS Sans

Serif字体

ANSI_FIXED_FONT 基于Windows字符集的固定字宽的字体。通常使用Courier字体ANSI_VAR_FONT 基于Windows字符集的变宽字体。通常使用MS Sans Serif字体DEVICE_DEFAULT_FONT 特定设备的缺省字体。该字体取决于GDI字体的映射程序如何解释字

体请求,因此,对于不同的设备,其字体宽度可能不同

OEM_FIXED_FONT Windows在MS-DOS命令提示窗口中使用,也称为终端字体,是一种

固定字宽的字体。随系统的不同而有所不同,在IBM及其兼容机上,

是与IBM-PC扩展字符集兼容的字符集

SYSTEM_FONT 系统字体。是基于Windows字符集的变宽字体。系统使用该字体来显

示窗口标题、菜单和对话框中的文本。系统字符集总是可用的,而其

他字符集则需要安装后才可使用

在早期的Windows版本中,系统字体是等宽字体,所有的字符都一样宽,类似于打字机所用的字体。从Windows 3.0开始,系统字体成为一种变宽字体,不同的字符可能具有不同的宽度,例如,“W”字符要比“i”字符宽。变宽字符比等宽字符更容易阅读。

应用程序可以很容易地使用这些字体。方法如下:

首先,调用函数GetStockObject获得某种常用字体的句柄,这里以OEM_FIXED_FONT为例。

hFont = GetStockObject ( OEM_FIXED_FONT );

然后,将该字体选入设备描述表。

SelectObject ( hdc, hFont );

每种设备都有一种缺省字体,对于显示器而言,其缺省字体就是系统字体。当第一次获得设备描述表句柄时,设备描述表中的字体就是缺省字体。如果应用程序不需要执行很多的文本输出,那么使用缺省字体即可。

程序3-1演示了Windows所提供的各种常用字体。

程序3-1 SelectFont.c

#include

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpcmdLine, int nCmdShow)

{

#第36页~

第3章 文本显示static TCHAR szAppName[] = TEXT("SelectFont");

static TCHAR szClassName[] = TEXT("SelectFontClass");

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 = szClassName;

// 注册窗口类

if ( !RegisterClass( &wndclass ) )

{

MessageBox( NULL, TEXT("This program requires Windows NT!"),

szAppName, MB_ICONERROR );

return 0;

}

// 创建窗口

hwnd = CreateWindow( szClassName,

TEXT("Select Different Fonts Program"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL );

// 显示窗口

ShowWindow( hwnd, nCmdShow );

#第37页~ Windows下的C/C++高级编程

// 更新窗口

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)

{

HDC hdc;

PAINTSTRUCT ps;

HFONT hCurrentFont,hOldFont;

switch( message )

{

case WM_CREATE:

return 0;

case WM_PAINT:

hdc = BeginPaint( hwnd, &ps );

// 更改当前字体为DEFAULT_GUI_FONT

hCurrentFont = GetStockObject( DEFAULT_GUI_FONT );

hOldFont = SelectObject( hdc, hCurrentFont );

TextOut( hdc, 0, 00, TEXT("DEFAULT_GUI_FONT"),

sizeof(TEXT("DEFAULT_GUI_FONT"))-1 );

// 更改当前字体为ANSI_FIXED_FONT

hCurrentFont = GetStockObject( ANSI_FIXED_FONT );

SelectObject( hdc, hCurrentFont );

TextOut( hdc, 0, 20, TEXT("ANSI_FIXED_FONT"),

sizeof(TEXT("ANSI_FIXED_FONT"))-1 );

// 更改当前字体为ANSI_VAR_FONT

#第38页~

第3章 文本显示

hCurrentFont = GetStockObject( ANSI_VAR_FONT );

SelectObject( hdc, hCurrentFont );

TextOut( hdc, 0, 40, TEXT("ANSI_VAR_FONT"),

sizeof(TEXT("ANSI_VAR_FONT"))-1 );

// 更改当前字体为DEVICE_DEFAULT_FONT

hCurrentFont = GetStockObject( DEVICE_DEFAULT_FONT );

SelectObject( hdc, hCurrentFont );

TextOut( hdc, 0, 60, TEXT("DEVICE_DEFAULT_FONT"),

sizeof(TEXT("DEVICE_DEFAULT_FONT"))-1 );

// 更改当前字体为OEM_FIXED_FONT

hCurrentFont = GetStockObject( OEM_FIXED_FONT );

SelectObject( hdc, hCurrentFont );

TextOut( hdc, 0, 80, TEXT("OEM_FIXED_FONT"),

sizeof(TEXT("OEM_FIXED_FONT"))-1 );

// 更改当前字体为SYSTEM_FONT

hCurrentFont = GetStockObject( SYSTEM_FONT );

SelectObject( hdc, hCurrentFont );

TextOut( hdc, 0, 100, TEXT("SYSTEM_FONT"),

sizeof(TEXT("SYSTEM_FONT"))-1 );

/************ 恢复原来的字体 **************/

SelectObject( hdc, hOldFont );

EndPaint( hwnd, &ps );

return 0;

case WM_DESTROY:

PostQuitMessage( 0 );

return 0;

}

return DefWindowProc( hwnd, message, wParam, lParam );

}

请注意程序中标明“恢复原来的字体”的地方,应当养成在使用完新的资源后恢复原先设置的良好习惯。该程序的效果如图3-4所示。

#第39页~ Windows下的C/C++高级编程

图3-4 不同的字体

3.2.5 字符大小

程序3-1中,通过在Y方向上逐次增加20个像素单位来显示后继行,这看上去并不太紧凑。为了紧凑地显示多行文本,就必须确定字体的字符大小,根据字符的高度来确定相邻两行文本之间正确的距离。有时我们还需要根据字符的宽度来确定X方向上后继字符的正确位置。

系统字体的字符高度和平均宽度(现在的Windows大多使用变宽字体)与显示器的分辨率有关。Windows系统允许用户在不同的分辨率下选择不同大小的系统字体。

应用程序可以通过调用GetTextMetrics函数来获得当前设备描述表中选定的字体信息。其定义如下:

BOOL GetTextMetrics(

HDC hdc, // 显示设备句柄

LPTEXTMETRIC lptm // 文本信息结构体指针

);

其中,lptm参数是一个指向TEXTMETRIC结构的指针。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;

TCHAR tmFirstChar;

TCHAR tmLastChar;

TCHAR tmDefaultChar;

TCHAR tmBreakChar;

BYTE tmItalic;

#第40页~

第3章 文本显示

BYTE tmUnderlined;

BYTE tmStruckOut;

BYTE tmPitchAndFamily;

BYTE tmCharSet;

} TEXTMETRIC;

该结构中有众多的字段,这里只讨论前面的7个。其中,前5个确定了字符的纵向大小,后两个确定了字符的横向大小。这些字段的定义如图3-5所示。

图3-5 字符大小参数定义图

其中,tmHeight是字符的纵向高度,它等于tmAscent和tmDescent的和。后两者分别为字符在基线以上和基线以下纵向的高度。

间距(Leading)是指打印机在两行文本之间插入的空间。在TEXTMETRIC结构中,间距分为内部间距(tmInternalLeading)和外部间距(tmExternalLeading)。其中,内部间距包括在tmAscent中(当然也就在tmHeight中),用于容纳重音符号。外部间距不包括在tmHeight中,是字体设计者建议加在相邻两行字符之间的空间。在具体使用时,应用程序可以使用它,也可以拒绝它。

在TEXTMETRIC结构中,有两个域来定义字符的宽度。其中,tmAveCharWidth表示字符的平均宽度,tmMaxCharWidth表示最宽的字符宽度。Windows中使用两种类型的字体:一类是固定宽度的字体,此时,tmAveCharWidth值与tmMaxCharWidth值相等;另一类是可变宽度字体,此时各个字符的宽度不尽相同,例如,“W”就比“i”要宽。

3.2.6 显示文本的例子

至此,在屏幕上显示多行文本的准备工作已经基本完成,下面就通过一个简单的例子程序来演示,见程序3-2。

程序3-2 SimpleDisplay.c

#include

#include

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpcmdLine, int nCmdShow)

#第41页~ Windows下的C/C++高级编程

{

static TCHAR szAppName[] = TEXT("SimpleDisplay");

static TCHAR szClassName[] = TEXT("SimDisplayClass");

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 = szClassName;

// 注册窗口类

if ( !RegisterClass( &wndclass ) )

{

MessageBox( NULL, TEXT("This program requires Windows NT!"),

szAppName, MB_ICONERROR );

return 0;

}

// 创建窗口

hwnd = CreateWindow( szClassName,

TEXT("Source Text Simple Display Program"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL );

// 显示窗口

#第42页~

第3章 文本显示

ShowWindow( hwnd, nCmdShow );

// 更新窗口

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 int cxChar,cyChar; // 用于记录字符宽度和字符高度

int cLineNumber; // 用于记录行数

TCHAR szBuff[256]; // 用于存放读入的字符

HDC hdc; // 显示设备句柄

PAINTSTRUCT ps; // 绘图信息结构体

TEXTMETRIC tm; // 字符信息结构体

FILE *fp; // 文件指针,用于打开、读写、关闭文件等操作

switch( message )

{

case WM_CREATE:

// 获得设备句柄

hdc = GetDC( hwnd );

// 获得字符信息,并确定字符宽度和字符高度

GetTextMetrics( hdc, &tm );

cxChar = tm.tmAveCharWidth;

cyChar = tm.tmHeight + tm.tmExternalLeading;

// 释放设备句柄

ReleaseDC( hwnd, hdc );

return 0;

case WM_PAINT:

hdc = BeginPaint( hwnd, &ps );

#第43页~ Windows下的C/C++高级编程

cLineNumber = 0;

// 以只读方式打开源文件SimpleDisplay.c

if( (fp=fopen("SimpleDisplay.c", "r")) != NULL )

{

// 如果尚未达到文件结尾处,则继续读取

while( !feof(fp) )

{

int i=0;

char ch;

// 从SimpleDisplay.c文件中读取字符,一次一个,直到一行结束或文件结束

while( (ch=fgetc(fp)) != '\n' && ch != EOF )

if( ch != '\t' ) // 去掉制表符

// 将读入的字符写入缓存

szBuff[i++] = ch;

// 显示读入的行

TextOut( hdc, cxChar, cyChar*cLineNumber, szBuff, i);

cLineNumber++;

}

// 关闭打开的文件

fclose(fp);

}

EndPaint( hwnd, &ps );

return 0;

case WM_DESTROY:

PostQuitMessage( 0 );

return 0;

}

return DefWindowProc( hwnd, message, wParam, lParam );

}

该程序通过文件操作打开其源文件SimpleDisplay.c,一次读入一个字符,写入缓存。通过判断读入的字符是否为换行符(’\n’)来确认一行读入完毕,然后调用TextOut函数将刚刚读入的一行在适当的位置显示出来。如此反复,直至文件结束。

这里通过计算cyChar*cLineNumber 来确定后继行的位置。其中cyChar的值由表达式:

cyChar = tm.tmHeight + tm.tmExternalLeading;

#第44页~

第3章 文本显示

确定,也就是字符的高度加上合理的行间距,使得多行文本的显示显得紧凑、合理。程序的效果如图3-6所示。

图3-6 SimpleDisplay效果图