API 入门学习之一 文本输出

来源:互联网 发布:日志分析软件 编辑:程序博客网 时间:2024/06/05 15:50
文本输出

一、绘制与重绘
在Windows系统中,程序只能在自己窗口的客户区中显示文本和图形,你不能保证它们在程序重新输出到那里之前还会留在那里。
Windows是一个消息驱动的系统。它使用两种方式把各种事件通知给应用程序:①把消息放在应用程序的消息队列中;②向适当的窗口过程直接发送消息。
1、WM_PAINT消息
除UpdateWindows函数直接产生WM_PAINT消息外,下列几种情况将产生WM_PAINT消息:
①用户移动一个窗口,导致原来被遮盖的部分窗口暴露出来;
②用户调整窗口的大小(当窗口的类型设定为CS_HREDRAW和CS_VREDRAW时);
③程序调用ScrollWindow或ScrollDC函数滚动客户区;
④程序调用InvalidateRect或InvalidateRgn函数显式生成WM_PAINT消息;
在某些情况下,当客户区的一部分被临时覆盖时,Windows会试图保存被覆盖的这部分,以便将来恢复时使用。这并不是每次都能成功。在以下情形,Windows可能会发送一条WM_PAINT消息: 
①Windows关闭一个覆盖了部分窗口的对话框或消息框;
②下拉菜单被拉下然后收回;
③显示提示信息;
在少数情况下,Windows总是会保存被覆盖部分的显示内容,然后再恢复。这些情况如下:
①鼠标指针在客户区内移动。
②在客户区内拖动图标。
处理WM_PAINT消息需要你改变对于视频输出的概念。你的程序应该收集并保存所有用于绘制客户区的信息,在收到WM_PAINT消息时再进行绘制。
如果程序需要在其它时候更新客户区,我们可以强制Windows生成WM_PAINT消息,比如使用InvalidateRect函数。
2、有效矩形和无效矩形
客户区更新时,仅更新需要更新的一小块(矩形)就行,多了浪费资源。如果客户区更新区域大且频繁,就会导致客户区闪烁。
客户区需要重新绘制的部分称为“无效区域”或“更新区域”。
在客户区中一旦有一个无效区域,将导致Windows在应用程序的消息队列中放置一条WM_PAINT消息。只有当程序客户区的一部分失效时,窗口过程才会接收到WM_PAINT消息。
Windows的内部为每一个窗口都保存了一个“绘制信息结构”。这个结构保存着一个可以覆盖任意窗口无效区域的最小矩形的坐标和一些其他的信息。这个最小矩形被称为“无效矩形”。如果窗口过程在处理一条等候处理的WM_PAINT消息之前,客户区中的另外一部分也失效了,那么Windows将计算出一个覆盖这两个失效部分的新的无效区域和无效矩形,并更新绘制信息结构中的数据。Windows不会在消息队列中放置多条WM_PAINT消息。
窗口过程可以通过调用InvalidateRect函数来强制使自己的客户区中的一个矩形失效。如果消息队列中已经有一条WM_PAINT消息,Windows就会计算出一个新的无效矩形,否则Windows会在消息队列里放置一条WM_PAINT消息。当窗口过程收到WM_PAINT消息时,窗口过程是知道无效矩形的坐标的。而在其它任何时候,窗口过程可以通过调用 GetUpdateRect函数来获取这些坐标。
当窗口过程在处理WM_PAINT消息时,在调用BeginPaint函数后,整个客户区会变成有效的。应用程序也可以通过调用ValidateRect函数来使客户区中任意的矩形变得有效。如果该函数调用的结果是让整个无效区域都有效,那么当前消息队列中的WM_PAINT消息就会被删除。
二、GDI简介
GDI,graphics device interface,图像装置界面,描绘图像并把它们转换到输出装置 (监视器, 打印机等)的窗口标准。这是维基百科的翻译,普通翻译为“图形设备接口”。
1、设备环境
设备环境(简称为DC),实际上是GDI内部维护的一个数据结构。设备环境与特定的显示设备(显示器或打印机)相关联。对于视频显示,设备环境通常与屏幕上的一个特定的窗口相关联。
(推测:设备环境这个数据结构中应该包含要使用的设备、特定窗口信息等部分。)
设备环境中的某些值是图形“属性”。这些属性决定了GDI绘图函数的工作细节。例如在TextOut函数中,设备环境的属性决定着文本的颜色、文本背景的颜色、函数的参数x和y如何映射到窗口的客户区,以及Windows用什么字体显示文本。
设备环境句柄是程序窗口使用GDI函数的“通行证”。有了设备环境句柄,就可以随心所欲地绘制你的客户区。
程序在客户区绘制前必须获取一个设备环境句柄。在获取句柄后,Windows会在内部的设备环境结构中填入默认的属性值(有些GDI函数可以改变这些默认值,有些GDI函数能获取这些属性的当前值,还有些GDI函数是真正在窗口的客户区进行绘图的)。
程序完成客户区的绘制后,必须释放设备环境句柄,设备环境句柄释放后不再有效,即不能被再使用。程序必须在处理同一条消息的过程中获取设备环境句柄和释放设备环境句柄,而不能在两条消息中间传递一个设备环境句柄,唯一的例外是通过调用CreateDC函数创建的设备环境。
2、绘制信息结构
Windows为每个窗口维护一个绘制信息结构。
初识绘制信息结构是在hellowin范例中的WndProc函数:
……
HDC         hdc;
PAINTSTRUCT ps;//声明绘制信息结构变量ps
RECT        rect ;
……
case WM_PAINT:

     hdc = BeginPaint(hwnd, &ps);//绘制信息结构变量ps作为参数被引用

注意:在这一段代码中“结构变量ps”从声明到被引用,你见到初始化了吗?书上说了“Windows为每个窗口维护一个绘制信息结构”,那么一定是Windows在ps被声明后到被引用前这段时间对ps进行了初始化,数据来源就是“Windows为每个窗口维护的那个绘制信息结构”。类似的实例还有msg这个结构变量(在WinMain函数中)。
PAINTSTRUCT类型定义在winuser.h中,字义如下:               
typedef struct tagPAINTSTRUCT { 

HDC  hdc;//设备环境句柄

  BOOLfErase;//是否擦除窗口背景,为真时用wndclass.hbrBackground指定的值

  RECTrcPaint;//给出无效矩形区域(不是矩形就按矩形去裁剪),类型为矩形结构

  BOOLfRestore;

  BOOLfIncUpdate;

  BYTErgbReserved[32];

} PAINTSTRUCT, *PPAINTSTRUCT;
注:rcPaint是RECT型变量,即矩形结构变量,由left、top、right、bottom构成,这几个成员是相对于窗口客户区、以像素为单位的。
在BeginPaint函数被调用时,Windows将自动填充这个结构中的字段,但程序只能够使用前三个字段,其余字段供windows内部使用。(那么hdc中应至少包含这前三个字段)
hdc = BeginPaint (hwnd, &ps);//哈哈!ps原来是这时被初始化的!
在处理WM_PAINT消息时,如果在调用BeginPaint函数之前执行了下面语句:

InvalidateRect(hwnd , NULL ,TRUE);

将导致整个客户区无效化,并使其后调用的BeginPaint函数擦除原有的背景。
在处理WM_PAINT消息时,如果在调用BeginPaint函数之前执行了下面语句:
InvalidateRect(hwnd , NULL , FALSE);
将导致整个客户区无效化,但其后调用的BeginPaint函数不会擦除原有的背景。
程序运行效率和速度是程序员需要考虑的:

对于Windows程序,最省事的做法是在收到WM_PAINT消息后,直接重绘整个客户区,不管rcPaint结构的值如何(不管有没有无效区)。这样编程时是方便了,但代价是操作系统的负担(程序反应速度慢)。假如我们的客户区是一张位图,且存在磁盘上,如果我们不使用rcPaint判断有没有无效区域,而是用省事的办法“InvalidateRect(hwnd, NULL , TRUE);”,那么就会造成频繁读取磁盘并对整个客户区进行绘制,这就相当浪费资源(客户区闪烁,里显示的东西更新慢)。

在处理WM_PAINT消息时,使用无效矩形(rcPaint)可以避免不必要的GDI函数调用。
3、获取设备环境句柄:方法一

通过BeginPaint函数获得:hdc =BeginPaint (hwnd, &ps);

BeginPaint(hwnd, &ps)通常出现在WM_PAINT消息处理模块,共有2个参数,第一参数是窗口句柄,第二个参数是绘制信息结构(PANITSTRUCT)变量ps(通常用这个名字)的地址。
hdc和ps在窗口过程里被使用前,必须提前声明(c++随用随声明)。
在处理WM_PAINT消息时,通常需要BeginPaint和EndPaint这2个函数,它们是成对出现的 (原因是用到了设备环境句柄,设备环境句柄使用后必须释放)。
通常处理WM_PAINT消息的代码如下:
case WM_PAINT:

     hdc = BeginPaint(hwnd, &ps);

     //GDI函数调用
   EndPaint (hwnd, &ps);
     return 0 ;
如果窗口过程中不专门处理WM_PAINT消息,那么DefWindowProc函数就会负责承担WM_PAINT消息处理的工作,形式为:
case WM_PAINT:

     hdc = BeginPaint(hwnd, &ps);

   EndPaint (hwnd, &ps);
     return 0 ;
看到了没有,BeginPaint和EndPaint这2个函数中间不包含任何代码,一切工作都由Windows来完成。
4、获取设备环境句柄:方法二
通过GetDC函数获得:hdc=GetDC(hwnd);
GetDC函数必须和ReleaseDC函数配对使用(设备环境句柄使用后必须释放)。通常的语法格式为:
hdc=GetDC(hwnd);
//GDI函数调用
ReleaseDC(hwnd,hdc);
注意:
GetDC函数返回的设备环境句柄与BeginPaint函数的设备环境句柄所包含的信息并不相同。
GetDC函数返回的设备环境句柄中包含的裁剪矩形是整个客户区,拿到这个句柄意味着你可以在客户区的任意位置绘制你想要的东西。同时,GetDC函数不会将客户区的无效区有效化,如果确实需要,可以使用ValidateRect(hwnd , NULL)。
BeginPaint函数返回的的设备环境句柄中包含的裁剪矩形可能是整个客户区,也可能不是。
不管是谁返回的设备环境句柄,它必定是和“客户区或整个窗口”相关的背景的句柄(无非是背景的大小不一样)。
记住“背景”这个词,它是你绘制的基础。
通常,GetDC和ReleaseDC函数用于处理键盘消息(例如在字处理程序中)或者鼠标消息(例如绘图程序中)。使用这两个函数,程序可以在收到用户的键盘或鼠标输入时及时地绘制客户区,而不必为了减少生成WM_PAINT消息去刻意使客户区的一部分无效化。但是,既使程序在处理非WM_PAINT消息时进行了绘制,它仍然必须收集足够的信息以便在收到WM_PAINT消息时能更新显示。
另一个与GetDC类似的函数是GetWindowDC,它返回的是整个窗口的设备环境句柄。这意味着程序可以使用从GetWindowDC返回的设备环境句柄在窗口的标题栏输出自已想要的东西(这就自绘吧)。当然要使用这个功能,程序的窗口过程必须处理WM_NCPAINT(非客户区绘制)消息。
5、TextOut函数详解
TextOut函数是显示文本的最重要的GDI函救,语法格式如下:
TextOut(hdc,x,y,psText,iLength); 
参数hdc是设备环境句柄,它既可以是从GetDC函数返回的hdc,也可以是处理WM_PAINT消息时从BeginPaint函数返回的hdc。
参数x和y决定着待输出字符串在客户区的起始位置。x是水平位置,y是垂直位置,待输出字符串的第一个字符的左上角定位在坐标点(x,y)。在默认的设备环境中,坐标原点位于客户区的左上角。如果传给TextOut的x和y值都是O,则待输出字符串将紧靠客户区的左上角。
参数psText是指向待输出字符串的指针。
参数iLength是待输出字符串中的字符数。待输出字符串中不应有任何ASCII控制字符,例如回车健、换行符、制表符或退格健。如果存在这些控制字符,Windows会把这些控制字符显示为空心或实心方块(奇怪符号)。TextOut并不认为字符串结尾的0表示字符申结束,它利用参数iLength来决定字符申的长度。
设备环境中的某些值是图形“属性”。这些属性决定了GDI绘图函数的工作细节。例如在TextOut函数中,设备环境的属性决定着文本的颜色(默认黑色)、文本背景的颜色(默认白色)、函数的参数x和y如何映射到窗口的客户区(相对客户区左上角坐标),以及Windows用什么字体显示文本。
在类似于TextOut函数的GDI绘图函致的文档中,传给函效的坐标值通常被称为”逻辑坐标。Windows有各种“映射模式”来决定怎样将GDI绘图函致中的逻辑坐标转换成显示器上的物理像素坐标,这些映射模式在设备环境中定义。默认的映射模式是MM_TEXT(标识符定义在WINGDI.H头文件中)。在MM_TEXT映射摸式下,逻辑单位和物理单位都是像索点,坐标是相对于容户区的左上角。x的值从左往右增大,y的值从上往下增大MM_TEXT中的坐标系统与Windows在PAINTSTRUCT结构中定义无效矩形的坐标系统是一致的。(然而在其他映射模式下就不是这么方便了。)
设备环境同时定义了一个裁剪区域。如前所述,从GetDC得到的设备环境句柄中,该裁剪区就是整个客户区;从BeginPaint得到的设备环境句柄中,该裁剪区域则是无效区城。在调用TextOut函数时,Windows将不会把字符串显示在剪区域外。如果一个字符只有一部分在裁剪区域里,那么只有这部分会显示。
6、系统字体
设备环境同时还定义了在调用TextOut时Windows使用的字体。默认的字体称为“系统字体”或SYSTEM_FONT(标识符定义在WINGDI.H头文件中)。系统字体是Windows在标题栏、莱单和对话框中使用的默认字体。在早期的Windows中,系统字体是一种等宽字体(所有字符的宽度是一样的,就像打字机那样)。但从Windows3.0开始,系统字体变成了变宽字体(每个字符的宽度是不一样的,例如,字符“W”就比字符“i”要宽)。研究表明,用变宽字体印刷的文章比用等宽字体印刷的可读性更强,其原理是当字符排列得更紧凑时,眼睛和大脑更容易注意到单词而不仅仅是一个个的字符。从等宽字体升级到变宽字体导致很多早期的 Windows程序不再适用,程序员们需要学习使用字体的新技术。系统字体是一种“点阵字体”(每个字符由像素点构成)。在某种程度上,系统字体中字符的大小取决于显示器的大小(系统字体的设计要求能够在显示器上起码显示25行80列字符)。
7、字符大小
为了用TextOut显示多行文本,就需要知道字体中字符的尺寸。由字符的高度确定下一行文字的垂直位置,由字符的平均宽度确定下一栏文本的水平位置。
那么系统字体中字符的平均宽度是多少?没有标准答案,因为这取决于显示器的分辨率。Windows要求的最低分辨率是科 640x480,但很多用户喜欢800x600或1024x768。而且,在这些高一点的分辨率下,用户还可以选择不同的系统字体的字号。
程序可以通过调用GetSystemMetrics函数来获取用户界面的尺寸,通过调用GetTextMetrics函数来获取字体尺寸。GetTextMetrics函数需要一个设备环境句柄,因为它会返回该设备环境中当前选定的字体的信息。Windows将把字符尺寸的各种值复制到类型为TEXTMETRIC的结构中,该结构在WINGDI.H头文件中定义,有20个字段,我们仅需关心其中前7个:
typedef struct tagTEXTMETRIC{

LONG tmHeight;

LONG tmAscent;

LONG tmDescent;

LONG tmInternalLeading;

LONG tmExternalLeading;

LONG tmAveCharWidth;

LONG tmMaxCharWidth;

……
}TEXTMETRIC, *PTEXTMETRIC;
这些字段的值的单位取决于设备环境中当前选定的映射模式。默认的映射模式是MM_TEXT,所以它们的值是以像素为单位的。在调用GetTextMetrics函数前,首先要定义一个结构变量,通常命名为tm:
TEXTMETRIC  tm;
需要获取字号时,首先获取设备环境句柄,然后调用GetTextMetrics:
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&tm);//获取字体尺寸
ReleaseDC(hwnd,hdc);
在这之后就可以检查文字尺寸结构中的值,并保存以后需要使用的部分。
8、文本尺寸的度量
在TEXTMETRIC结构中有设备环境中当前字体的各种信息。但字体的纵向尺寸仅由其中的5项决定,下面的图显示了其中的4项。
其中最重要的是tmHeight,它是tmAsent和tmDescent的和。这2个值分别是字符在基线之上和基线之下的最大宽度。间距(leading)是两行文字之间的空间。在TEXTMETRIC结构中,内部间距(tmInternalLeading)包含在tmAscent中,当然也就包含在tmHeight中。该间距通常用于显示重音符号。tmInternalLeading的值可以设为0,在这种情况下,带重音符号的字符会稍微短一点,以便把重音符号包括在内。
0 0
原创粉丝点击