第07章 图形操作 · 7.1 GDI原理(2)

来源:互联网 发布:cad画图软件下载 编辑:程序博客网 时间:2024/06/02 05:16

3. 探讨WM_PAINT消息

当客户区被覆盖并重新显示的时候,Windows并不是在所有的情况下都发送WM_PAINT消息,下面是几种不同的情况:

● 当鼠标光标移过窗口客户区以及图标拖过客户区这两种情况,Windows总是自己保存被覆盖的区域并恢复它,并不需要发送WM_PAINT消息通知用户程序。

● 当窗口客户区被自己的下拉式菜单覆盖,或者被自己弹出的对话框覆盖后,Windows会尝试保存被覆盖的区域并在以后恢复它,如果因为某种原因无法保存并恢复的话,Windows会发送一个WM_PAINT消息通知程序。

● 别的情况造成窗口的一部分从不可见变到可见,如程序从最小化的状态恢复,其他的窗口覆盖客户区后移开,用户改变了窗口的大小和用户按动滚动条等,在这些情况下,Windows会向窗口发送WM_PAINT消息。

● 一些函数会引发WM_PAINT消息,如UpdateWindow,InvalidateRect以及InvalidateRgn函数等。

窗口过程收到WM_PAINT消息后,并不代表整个客户区都需要被刷新,有可能客户区被覆盖的区域只有一小块,这个区域就叫做“无效区域”,程序只需要更新这个区域。

和WM_TIMER消息类似,WM_PAINT消息也是一个低级别的消息,虽然它不会像WM_TIMER消息一样被丢弃,但Windows总是在消息循环空的时候才把WM_PAINT放入其中,实际上,Windows为每个窗口维护一个“绘图信息结构”,无效区域的坐标就在其中,每当消息循环空的时候,如果Windows发现存在一个无效区域,就会放入一个WM_PAINT消息。

无效区域的坐标并不附带在WM_PAINT消息的参数中,在程序中有其他方法可以获取,WM_PAINT消息只是通知程序有个区域需要更新而已,所以Windows也不会同时将两条WM_PAINT消息放入消息循环,当Windows要放入一条WM_PAINT消息的时候,如果发现已经存在一个无效区域了,那么它只需要把新旧两个无效区域合并计算出一个新的无效区域就可以了,消息循环中还是只需要一条WM_PAINT消息。

由于存在“无效区域”这样一个东西,所以程序在WM_PAINT消息中对客户区刷新完毕后工作并没有结束,如果不使无效区域变得有效,Windows会在下一轮消息循环中继续放入一个WM_PAINT消息。还记得4.4.2节中的实验4吗,如果没有这个环节,WM_PAINT消息就会源源不断地发过来!那个实验中我们并没有去刷新客户区,而是简单地用一个ValidateRect函数直接让客户区变得有效,以此来“欺骗”Windows已经没有无效区域了,当Windows检查“绘图信息结构”的时候发现没有了无效区域,也就不会继续发送WM_PAINT消息了。

WM_PAINT消息的处理流程一般是:

.if    eax ==  WM_PAINT  ;eax为uMsg       invoke  BeginPaint,hWnd,addr stPS       ;刷新客户区的代码       invoke  EndPaint,hWnd,addr stPS       xor     eax,eax       ret

读者可以发现中间并没有调用ValidateRect来使无效区域变得有效,这是因为BeginPaint函数和EndPaint函数隐含有这个功能,如果不是以BeginPaint/EndPaint当做消息处理代码的头尾的话,那么在WM_PAINT消息返回的时候就必须调用ValidateRect函数。

BeginPaint函数的第二个参数是一个绘图信息结构的缓冲区地址,Windows会在这里返回绘图信息结构,结构中包含了无效区域的位置和大小,绘图信息结构的定义如下:

PAINTSTRUCT STRUCT hdc          DWORD     ? fErase       DWORD     ? rcPaint      RECT      <> fRestore     DWORD     ? fIncUpdate   DWORD     ?  rgbReserved  BYTE 32 dup(?)PAINTSTRUCT ENDS

其中hdc字段是窗口的设备环境句柄(在下一节中将要讲到),rcPaint字段是一个RECT结构,它指定了无效区域矩形的对角顶点,fErase字段如果为非零值,表示Windows在发送WM_PAINT消息前已经用背景色擦除了无效区域,后面3个字段是Windows内部使用的,应用程序不必去理会它们。

7.1.2 设备环境

好了,解决了“When”的问题,让我们来考虑一个新的问题,在DOS操作系统中,向屏幕输出数据实际上是把输出内容拷贝到视频缓冲区中,在第01章的图1.1中就已经说明:如果在文本模式下显示信息,只需要把内容拷贝到B8000h处的内存中;显示图形信息,可以把图形数据拷贝到A0000h处的内存中。

在Windows中,GDI接口把程序和硬件分隔开来,在Win32编程中,再也不能通过直接向视频缓冲区拷贝数据的办法来显示信息了,那么,究竟该往哪里输出图形呢——这就是“Where”的问题。答案是:通过“设备环境”来输出图形。

1. 什么是设备环境

在Windows中,所有与图形相关的操作都是用统一的方法来完成的(不然就不能称为“图形设备接口”了)。不管是绘画屏幕上的一个窗口,还是把图形输出到打印机,或者对一幅位图进行绘画,使用的绘图函数都是相同的,为了实现方法上的统一,必须将所有的图形对象看成是一个虚拟的设备,这些设备可能有不同的属性,如黑白打印机和彩色屏幕的颜色深度是不同的,不同打印机的尺寸和分辨率可能是不同的,绘图仪只支持矢量而不支持位图等。不同设备的不同属性就构成了一个绘图的“环境”,就像DOS操作系统中把视频缓冲区当做图形操作的对象一样,这个绘图的“环境”就是Win32编程中图形操作的对象,把它叫做“设备环境”。设备环境实际上是一个数据结构,结构中保存的就是设备的属性,当对设备环境进行图形操作的时候,Windows可以根据这些属性找到对应的设备进行相关的操作。

在实际使用中,通过“设备环境”可以操作的对象很广泛,除了可以是打印机或绘图仪等硬件设备外,也可以是窗口的客户区,包括大大小小的所有可以被称为窗口的按钮与控件等的客户区,也可以是一个位图。总之,任何需要用到图形操作的东西都可以通过“设备环境”进行绘图。

为了更好地理解“设备环境”是什么,先来看一个例子,例子的代码在所附光盘的Chapter07\DcCopy目录中,DcCopy.asm中的代码如下:

                  .386                .model flat,stdcall                  option casemap:none;####################################################################; Include 文件定义;####################################################################include        windows.incinclude        gdi32.incincludelib     gdi32.libinclude        user32.incincludelib     user32.libinclude        kernel32.incincludelib     kernel32.lib;####################################################################ID_TIMER          equ 1;####################################################################; 数据段;####################################################################                  .data?hInstance      dd     ?hWin1          dd     ?hWin2          dd     ?                   .constszClass1          db 'SourceWindow',0szClass2          db 'DestWindow',0szCaption1    db '请尝试用别的窗口覆盖本窗口!',0szCaption2    db '本窗口图像拷贝自另一窗口',0szText         db 'Win32 Assembly, Simple and powerful !',0;####################################################################                  .code;####################################################################; 定时器过程;####################################################################_ProcTimer    proc      _hWnd,uMsg,_idEvent,_dwTime                 local  @hDc1,@hDc2                 local  @stRect:RECT                  invoke GetDC,hWin1                 mov    @hDc1,eax                 invoke GetDC,hWin2                 mov    @hDc2,eax                 invoke GetClientRect,hWin1,addr @stRect                 invoke BitBlt,@hDc2,0,0,@stRect.right,@stRect.bottom,\                        @hDc1,0,0,SRCCOPY                 invoke ReleaseDC,hWin1,@hDc1                 invoke ReleaseDC,hWin2,@hDc2                 ret _ProcTimer     endp;####################################################################; 窗口过程;####################################################################_ProcWinMain      proc       uses ebx edi esi,hWnd,uMsg,wParam,lParam                  local  @stPs:PAINTSTRUCT                 local  @stRect:RECT                 local  @hDc                  mov    eax,uMsg                 mov    ecx,hWnd;********************************************************************                 .if eax == WM_PAINT && ecx == hWin1                     invoke BeginPaint,hWnd,addr @stPs                     mov    @hDc,eax                     invoke GetClientRect,hWnd,addr @stRect                     invoke DrawText,@hDc,addr szText,-1,\                             addr @stRect,\                             DT_SINGLELINE or DT_CENTER or DT_VCENTER                     invoke EndPaint,hWnd,addr @stPs;********************************************************************                 .elseif eax == WM_CLOSE                         invoke PostQuitMessage,NULL                         invoke DestroyWindow,hWin1                         invoke DestroyWindow,hWin2;********************************************************************                 .else                         invoke DefWindowProc,hWnd,uMsg,wParam,lParam                         ret                 .endif;********************************************************************                 xor eax,eax                 ret _ProcWinMain   endp;####################################################################_WinMain      proc              local  @stWndClass:WNDCLASSEX              local  @stMsg:MSG             local  @hTimer                        invoke GetModuleHandle,NULL             mov    hInstance,eax             invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass;********************************************************************             invoke LoadCursor,0,IDC_ARROW             mov    @stWndClass.hCursor,eax             push       hInstance             pop    @stWndClass.hInstance             mov    @stWndClass.cbSize,sizeof WNDCLASSEX             mov    @stWndClass.style,CS_HREDRAW or CS_VREDRAW             mov    @stWndClass.lpfnWndProc,offset _ProcWinMain             mov    @stWndClass.hbrBackground,COLOR_WINDOW + 1             mov    @stWndClass.lpszClassName,offset szClass1             invoke RegisterClassEx,addr @stWndClass             invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClass1,\                     offset szCaption1,WS_OVERLAPPEDWINDOW,\                     450,100,300,300,\                     NULL,NULL,hInstance,NULL             mov    hWin1,eax             invoke ShowWindow,hWin1,SW_SHOWNORMAL             invoke UpdateWindow,hWin1;********************************************************************             mov    @stWndClass.lpszClassName,offset szClass2             invoke RegisterClassEx,addr @stWndClass             invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClass2,\                     offset szCaption2,WS_OVERLAPPEDWINDOW,\                     100,100,300,300,\                     NULL,NULL,hInstance,NULL             mov    hWin2,eax             invoke ShowWindow,hWin2,SW_SHOWNORMAL             invoke UpdateWindow,hWin2;********************************************************************; 设置定时器;********************************************************************             invoke SetTimer,NULL,NULL,100,addr _ProcTimer             mov @hTimer,eax;********************************************************************; 消息循环;********************************************************************             .while TRUE                     invoke GetMessage,addr @stMsg,NULL,0,0                     .break .if eax == 0                     invoke TranslateMessage,addr @stMsg                     invoke DispatchMessage,addr @stMsg             .endw;********************************************************************; 清除定时器;********************************************************************             invoke KillTimer,NULL,@hTimer             ret _WinMain      endp;####################################################################start:              call       _WinMain             invoke ExitProcess,NULL;####################################################################             end    start
原创粉丝点击