1. 客户区的完整概念:
即应用程序窗口中没有被标题栏、边框、菜单栏、工具栏、状态栏和滚动条占据的中间的一片区域,用户可以在这片区域内绘制任意信息
注意!我们不能假定它有特定的尺寸或者是它的尺寸不会发生变化,窗口必须和其它应用程序窗口共用仅有的一块显示屏幕,因此要着重处理窗口不够用(输出的信息太多)以及窗口太大(输出信息少,空白区域多)这两种情形以使窗口资源得到充分利用
!由于窗口可以在屏幕中任意移动或放缩,因此不能假定位置以及字体的大小,而必须通过Windows的工具获得运行时的环境信息(即一种相对量)来进行相关问题的处理
2. 关于WM_PAINT消息的补充:
以下事件发生时会产生WM_PAINT消息要求客户区重画(WM_PAINT消息总是非队列消息,要求立即响应的):
被遮盖部分暴露出来、尺寸改变(必须要设定CS_HREDRAW和CS_VREDRAW,否则就是固定窗口)、滑条滚动窗口、关闭遮住部分窗口的对话框和消息框、下拉菜单被收回(下拉时遮住部分客户区)、显示提示信息、直接使用InvalidateRect和ValidateRgn函数显示生成WM_PAINT消息(之后会介绍)
在以上情形中系统都不会保存别遮住部分的信息,因为特别耗资源,因此都是直接对客户区中需要重绘的区域进行重现,接下来两个情况系统会保存遮盖部分,因为遮盖面积非常小,为了速度值得保存:
鼠标在客户区内移动、客户区内拖动图标
3. 有效区域以及有效矩形的概念:
WM_PAINT重绘时并不需要重绘整个客户区重绘而只需要重绘那些被抹掉的部分,而需要重新绘制的部分就成为“无效区域”或“更新区域”
在处理WM_PAINT响应的时候必须使用BeginPaint让无效区域有效化(即刷新无效区域的背景),否则永远也无法使无效区域有效,即使直接使用DrawText等函数直接绘制(因为背景还没有抹干净),如果存在无效区域就会在消息队列中投递WM_PAINT消息,如果响应WM_PAINT中不写BeginPaint和EndPaint则无效区域永远不可能有效,因此会不停发送WM_PAINT消息时程序进入死循环!
即使是DefWindowProc也是如此处理的:
case WM_PAINT:
BeginPaint( hWnd, &ps );
EndPaint( hWnd, &ps );
return 0;
有效矩形:
首先RECT保存有四个LONG型值:left、top、right、bottom,表示矩形的左上角坐标以及右下角坐标,但都是逻辑值而不是绝对值(即窗口左上角坐标设为(0, 0)),计算绝对坐标时会利用窗口左上角坐标的绝对值,这样有利于通用编程,因为不同显示屏分辨率不一样、大小不一样、像素点个数不一样,难以用统一的坐标来表示
Windows内部会为每个窗口保存一个绘制信息结构,保存着可以覆盖无效区域的最小矩形的信息(保存在RECT里,还有就是无效区域形状可能不规则,只能用一个最小面积矩形把这些区域统统覆盖,因此无效矩形>无效区域),这就是无效矩形,如果在处理消息队列中的WM_PAINT消息之前又多了一块无效区域,则Windows会将这块区域和之前的无效区域整合并算出一个新的无效矩形更新WM_PAINT消息的相关信息,因为消息队列里只能有一个WM_PAINT消息而不能有多个!
4. 绘制信息结构——PAINTSTRUCT:
typedef struct tagPAINTSTRUCT{ // 绘制结构
HDC hDC; // 即BeginPaint获得的hDC,表现出来Windows典型的数据冗余性
BOOL rErase; // 标志背景是否该被擦除(即重刷),TRUE表示要重刷
// FALSE一般表示已经重刷过了不必重刷
// 当调用完BeginPaint之后该项就被设为了FALSE了,因为已经重刷过了
RECT rcPaint; // 保存无效矩形区域的边界信息(逻辑值)
// 即在接下来的各种使用hDC绘制信息的时候就会限制在这里指定的矩形区域中进行绘制
... // 接下来的字段供Windows内部使用,用户用不到
} PAINTSTRUCT, *PPAINTSTRUCT;
在BeginPaint之前可以简单暴力地使用InvalidateRect函数将整个客户区标记为无效(需要重新绘制整个客户区):InvalidateRect( hWnd, NULL, TRUE );
但一般都不使用,因为重绘整个客户区会比较费时费资源
BOOL InvalidateRect(
HWND hWnd, // 需要标记的窗口
CONST RECT* lpRect, // 需要处理的矩形区域
BOOL bErase // 是否要被擦除
);
5. 处理非WM_PAINT消息时获取DC以及获取非客户区的DC:
由于WM_PAINT消息处理时必须要使无效区域有效化而不得不使用BeginPaint,但是在处理其它消息,如键盘和鼠标消息的时候为了及时绘制信息而不需要重绘,可以直接使用GetDC函数获取客户区DC,注意GetDC( hWnd )和ReleaseDC( hWnd, hDC )必须成对出现,用完一定要及时释放DC资源
但有时候不得不处理整个窗口的绘制(比如标题栏等),这是就得用GetWindowDC了(也和ReleaseDC配合使用),可以响应WM_NCPAINT消息(即None-Client Area的意思,即绘制非客户区的消息)
DC作用的区域:
如果是从BeginPaint中获得的DC,则其作用区域(即裁剪区)就是无效矩形区域,如果是从GetDC中获得的DC,则其裁剪区域就是整个客户区
6. TextOut函数详解:
是显示文本的一个最为重要的GDI函数,位于wingdi.h头文件中,其原型为:
BOOL TextOut(
HDC hdc, // 利用该hDC进行绘制
int nXStart, // 文本第一个字符的左上角坐标定位于该逻辑坐标上
int nYStart, // 即客户区的坐标系中(客户区左上角设为(0, 0))
LPCTSTR lpString, // 输出的文本
// 不能含有ASCII控制符,如\n等,会将其显示为空心或实心的方块!
int cbString // 文本的长度,必须指定长度,不能以'\0'作为结束标志
); // 成功返回非0,失败返回0
关于字符框:
即DC中保存的默认的文