MFC----设备上下文

来源:互联网 发布:服装设计用什么软件好 编辑:程序博客网 时间:2024/05/29 15:48
关于设备上下文(HDC)专题

一、句柄与HDC
      “句柄”是整个windows编程的基础,它通常是一个四字节长(32位的)的整数值,用来唯一标识应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等,应用程序能够通过句柄访问相应的对象的信息。但是,句柄不是一个指针,程序不能利用句柄来直接阅读它所指示的信息。如果句柄不用在I/O文件中,它是毫无用处的。在VC中,“H”开头的大多数是句柄,MFC类的前缀都是C开头的。
       HDC是DC的句柄。一个DC(device context,称为“设备上下文”或“设备内容”)是一个结构体,它定义了图形对象的参数设置以及它们的属性,还有影响输出结果的模式选择。图形对象包括画线的笔,绘图和填充用的画刷,可以复制的位图或者屏幕的卷动,用于可用颜色定义的调色板,还有进行其他操作的区域,绘图的路径等等。一个DC的绘图对象包括:Bitmap、Brush、Palette、Font、Path、Pen、Region。DC的类型包括:显示器、打印机、存储器和数据的索引。
       当想要在图形输出设备(例如屏幕或打印机)上绘制图形时,必须首先获得设备上下文的句柄。先给出这个句柄,Windows才允许程序使用设备,在GDI函数中将句柄作为一个参数传入,向Windows标明需要使用的设备。设备上下文中包含许多属性,当GDI在不同的设备上工作时都要用到这些属性。使用这些属性可使GDI只关心起始和终止坐标的大小,而不必关心有关对象的其他属性,如颜色、背景等等,因为这些都是设备上下文的一部分。当需要修改这些属性时,只需调用一个修改设备上下文中属性的参数,以后的程序中都使用修改后的设备上下文属性。

二、如何取得设备上下文句柄
1、如何获得窗口的设备上下文句柄      

       设备上下文(DC)是连接Windows应用程序、设备驱动程序以及输出设备的纽带。获取设备上下文句柄有多种方法,最一般的方法是当处理一条消息时获得了设备上下文、并在退出窗口之前释放它。一般的处理方法如下:
方法一:使用BeginPaint()和EndPaint()
在处理WM_PAINT消息时,使用BeginPaint()和EndPaint(),如:
hdc = BeginPaint (hwnd, &ps) ;
   使用GDI函数
EndPaint (hwnd, &ps) ;
这里的hdc是BeginPaint()返回的设备上下文句柄,有了从BeginPaint()获取的设备上下文句柄,就可以也只能在ps指出的rcPaint的矩形内绘图,EndPaint调用使这一区域有效。其中,ps---> 绘图信息结构如下:
typedef  struct  tagPAINTSTRUCT
{
   HDC  hdc ;      \\设备内容句柄
   BOOL  fErase ;   \\如果为FALSE(0),这意味着系统已经擦除了无效区域的背景
   RECT  rcPaint ;   \\是RECT型态的结构(为left、top、right和bottom)
   BOOL  fRestore ;
   BOOL  fIncUpdate ;
   BYTE  rgbReserved[32] ;
}  PAINTSTRUCT ;
使用者程序只使用前三个字段,其它字段由Windows内部使用。
处理WM_PAINT消息时,必须成对地呼叫BeginPaint和EndPaint。如果窗口消息处理程序不处理WM_PAINT消息,则它必须将WM_PAINT消息传递给Windows中DefWindowProc(内定窗口消息处理程序)。

方法二:使用函数GetDc()| ReleaseDC()
使用使用这种方法获取和释放设备上下文可以在整个客户区域内画图,图形在整个客户区域内都有效:
  hdC=GetDc(hwnd);
     …画图操作…
      ReleaseDC(hwnd ,hdc);

一般可以呼叫GetDC和ReleaseDC来对键盘消息(如在字处理程序中)和鼠标消息(如在画图程序中)作出反应。此时,程序可以立刻根据使用者的键盘或鼠标输入来更新显示区域,而不需要考虑为了窗口的无效区域而使用WM_PAINT消息。不过,一旦确实收到了WM_PAINT消息,程序就必须要收集足够的信息后才能更新显示。

方法三:获取整个窗口的设备上下文
获取和释放设备上下文,可以在整个窗口内画图,图形在整个窗口内有效:
  hdC=GetWindowDc(hwnd);
     …画图操作…
  ReleaseDc(hwnd,hdc);

注意:GetDC传回用于写入窗口客户区域的设备上下文句柄,而GetWindowDC传回写入整个窗口的设备上下文句柄。 获得的设备上下文覆盖了整个窗口(包括非客户区),例如标题栏、菜单、滚动条,以及边框等等。这使得程序能够在非客户区域实现自定义图形,例如自定义标题或者边框。当不再需要该设备上下文时,需要调用ReleaseDC函数释放它。

2、内存设备上下文
    CreateCompatibleDC 创建一个与指定设备兼容的内存设备上下文(DC)。请注意,通过GetDc()获取的HDC直接与相关设备沟通,而本函数创建的DC,则是与内存中的一个表面相关联。
函数原型:
HDC  CreateCompatibleDC(HDC  hdc);
参数说明:
hdc:现有设备上下文的句柄,如果该句柄为NULL,该函数创建一个与应用程序的当前显示器兼容的内存设备上下文环境。
返回值:
如果成功,则返回内存设备上下文的句柄;如果失败,则返回值为NULL。
示例:
hdcMem = CreateCompatibleDC(hdc);
…画图操作…
DeleteDc(hdcMem );
注释:
        内存设备上下文是仅在内存中存在的设备上下文,当内存设备上下文被创建时,它的显示界面是标准的一个单色像素宽和一个单色像素高,在一个应用程序可以使用内存设备上下文进行绘图操作之前,它必须选择一个高和宽都正确的位图到设备上下文环境中。我们可以通过使用CreateCompatibleBitmap函数指定高、宽和色彩组合以满足函数调用的需要。
  当一个内存设备上下文环境创建时,所有的特性都设为缺省值,内存设备上下文环境作为一个普通的设备上下文环境使用,当然也可以设置这些特性为非缺省值,得到它的特性的当前设置,为它选择画笔,刷子和区域。

3、获取设备上下文信息
       一个设备上下文通常涉及物理设备,如视频显示器、打印机等,所以需要获取有关该设备的信息,如显示器大小和彩色能力等。可以通过调用GetDeviceCaps函数来获取这样的信息,例如:
nValue = GetDeviceCaps(hdc,nIndex);
  这里的hdc标识设备上下文,nIndex确定返回值,它可以是window.h中所定义的28个标识符中的一个,例如nIndex = DRIVEVERSION,则该函数返回的是版本号。真正影响在用户区域上绘制过程的设备上下文属性是“映射方式”,与映射方式属性密切相关的还有如下四个设备上下义属性:窗口原点、视窗原点、窗口范围和视窗范围。Windows定义了八种映射方式,可以调用函数setMapMode(hdc,MapMode)来设置这八种映射方式中的一种。其中,hdc用来标识设备上下文,nMapMode可以取MM_TEXT、MM_LOMETRIC、MM_HIMETRIC等八个中的一个。在设置了映射方式之后,到下一次设置映射方式之前,Windows一直使用这种映射方式。如果想要获取当前的映射方式,可用:
  nMapMode = GetMapMode(hdc)
  在设置了映射方式之后,就规定了逻辑单位的大小和增量的方式,在GDI画图函数中,可以不必考虑这些内容而直接使用逻辑数字,如:
  SetMapMode(hdc ,MM_TEXT);
  TextOut(hdc,8 ,16,szBuffer ,nLength)
即正文从用户区域左起第八个象素,顶边起第16个象素的位置开始写操作。不管映射方式如何,Windows函数中所有坐标规定为-32768 到 32767之间的带符号短整救。

三、对Windows设计中的一些区域的介绍
1、客户区域与非客户区域
       整个窗体一般分客户区和非客户区。窗体的非客户区包括窗口的标题栏(包括最大/最小、关闭和帮助按纽)、菜单栏、工具栏(条)、状态栏、窗口四周边框(大概2个象素宽)。用户不能对非客户区进行(例如输出、绘图等)操作。分客户区,顾名思义就是客户可以进行各种操作的区域,窗体中除去非客户区外都是客户区。一般窗体控件的全部区域都是客户区(默认)。
在Win 32中,获得窗体的客户区的函数是:
BOOL GetClientRect(HWND hWnd, LPRECT lpRect );
获得整个窗体区域的函数是:
BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);

2、无效区域
        Windows每次绘图时是针对某个区域来进行的,这个区域就是无效区域。无效区域就是指需要重画的区域。所谓无效,就是告诉Windows这块区域允许重绘,但之前这里有什么东西是不用管的,Windows会在另外一块画布上绘制当前的内容,然后将它贴到屏幕上。假设A是新弹出的一个对话框或被激活的现有对话框,A对话框置于原来的活动对话框B前面,造成对话框B的部分或全部被覆盖,当对话框A移开或关闭后,使对话框B原来被覆盖的地方重新可见。那部分被覆盖的地方就称为无效区域。
       只有当一个窗口消息空闲时,系统才会抽空检查一下这个窗口的无效区域是否为非空(WM_PAINT的优先级是最低的。这也就是为什么系统很忙时窗口和桌面往往会出现变白、刷新不了、留拖拽痕迹等现象的原因),如果非空,系统就发送WM_PAINT。所以一定要用BeginPaint、EndPaint把无效区域设为空,否则系统将一直发送WM_PAINT消息。
         为什么WINDOWS要提出无效区域的概念?这是为了加速。因为BeginPaint和EndPaint用到的设备描述符只会在当前的无效区域内绘画,在有效区域内的绘画会自动被过滤,大家都知道,WIN GDI的绘画速度是比较慢的,所以能节省一个象素就节省一个,不用吝啬,这样可以有效加快绘画速度。

3、显示区域
         显示区域这个要根据不同的语境去辨别。
         一般,我们说窗口(框架)的显示区域是指窗口的客户区域。
         整个显示区域一般是相对显示器而言,指的是屏幕的整个显示区域,即满屏区域。
        在这里我们要注意到,UpdateWindow与WinMain中用来产生第一个WM_PAINT消息的时候,即最初建立窗口时,整个显示区域(就是满屏区域)内容变为无效区域,而后面获得的显示区域一般都是指的客户区域。
四、BeginPaint()|EndPaint() 与 GetDC()|ReleaseDC()
1、两组函数的区别
    第一种情况:
case WM_PAINT:
      gdc = BeginPaint (hwnd, &ps);
          TextOut (gdc, 0, 0, s, strlen (s));  //显示出来的字很正常
     EndPaint (hwnd, &ps);
break;
第二种情况:
case WM_PAINT:
      gdc = GetDC (hwnd);
      TextOut (gdc, 0, 0, s, strlen (s));  //当输出的文字比较多时显示的字不停闪烁
      ReleaseDC (hwnd, gdc);
break;  
以上两种绘屏方法有什么区别呢?区别主要在于获得设备上下文的函数BeginPaint()、和GetDC()间的区别:
(1)、BeginPaint()函数只是针对无效区域进行重画,使无效区域变成有效。在WM_PAINT消息处理期间,窗口消息处理程序在呼叫了BeginPaint()之后,系统会“清空”全部无效区域,使整个显示区域变为有效可见。BeginPaint()是比较“被动”的,只有在无效区域存在时才会重画,而GetDC()函数则跟无效区域没有关系,是一种“主动”绘画,它不加判断就都画上去,只要你指到哪,它就画到哪。因此,在使用GetDC()函数重新绘图后,往往用ValidateRect ()函数使无效区域变为有效,其函数函数原型如下:
BOOL ValidateRect(HWND  hWnd,   //窗口的句柄
  CONST RECT  *lpRect );  //指向RECT结构的指针
如果需要使整个显示区域有效,可以呼叫:
ValidateRect (hwnd, NULL) ;

(2)、BeginPaint()函数在对无效区域进行重画的同时会删除消息队列中的WM_PAINT消息,WM_PAINT消息处理完返回给系统后,系统不会再重发WM_PAINT消息,除非人为调用SendMessage()、PostMessage()或UpdateWindow()等语句“强迫”系统产生一个WM_PAINT消息去进行显示区域重绘。而GetDC()函数既不删除也不能使无效区域变为有效区域,因此当程序跳出 WM_PAINT 时,无效区域仍然存在,所以系统会不断发送WM_PAINT消息,于是程序不断处理WM_PAINT消息,这就是出现显示的文字不停闪烁的原因了。在绘画任务量较大时,往往会导致程序卡死。

(3)、BeginPaint()必须和EndPaint()配套使用,并且这两个函数也只能用在WM_PAINT消息的相应函数当中;而GetDC()必须和ReleaseDC()配套使用,且可以出现在各种消息处理中而不仅仅是WM_PAINT消息。另外,在WM_PAINT消息中使用BeginPaint()和EndPaint()函数时,绘制的图形背景不会被抹掉,而在WM_PAINT消息中使用GetDC()和ReleaseDC()函数绘制图形时,绘制的图形背景会被系统默认的WM_ERASEBKGND消息的响应函数的背景绘制所抹掉,因此要在WM_PAINT消息中用GetDC()和ReleaseDC()函数替代BeginPaint()和EndPaint()函数,必须在消息处理函数(窗口过程)中加上对消息WM_ERASEBKGND的处理,使系统不对WM_ERASEBKGND执行默认的处理如下所示:

LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)

 {

         switch(wMsg)

          { 

                 case WM_PAINT:

                        gdc = GetDC (hwnd);
                          //绘制图形代码段

                       ReleaseDC (hwnd, gdc);

                      break;

                 case WM_ERASEBKGND    

                        return 0;       

                .........

               defaul:

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

         }

         return 0;

 }  

(4)、两者传回的设备上下文句柄不同。BeginPaint()传回的设备上下文句柄的区域仅仅是需要重绘的无效区域,而GetDC()传回的设备上下文句柄的区域是一个剪取矩形,它等于整个显示区域(或称客户区域)。因此,GetDC()可以在显示区域(客户区域)的某一部分绘图,而不只是在无效矩形上绘图(如果确实存在无效矩形)。

(5)、BeginPaint()函数常常和InvalidateRect()函数配合使用。
    InvalidateRect()函数原型如下:
BOOL InvalidateRect(HWND hWnd, CONST RECT *lpRect,BOOL bErase);  
参数说明:
①.hWnd:要更新的客户区所在的窗体的句柄。如果为NULL,则系统将在函数返回前重新绘制所有的窗口, 然后发送 WM_ERASEBKGND 和 WM_NCPAINT 给窗口过程处理函数。
②.lpRect:无效区域的矩形代表,它是一个结构体指针,存放着矩形的大小。如果为NULL,全部的窗口客户区域将被增加到更新区域中。
③.bErase:指出无效矩形被标记为有效时,是否擦除背景。如果该参数为“TRUE”,在调用BeginPaint()函数时,无效区域的背景将被擦除,反之则保留原背景。

注意,被标记为无效矩形的区域直到WM_PAINT消息被处理完之后才会消失,或者使用ValidateRect(),ValidateRgn()函数来使之有效。当应用程序的消息队列中为空时,并且窗体要更新的区域非空时,系统会发送一个WM_PAINT消息到窗体。
如果您希望立即更新无效区域,可以在呼叫InvalidateRect之后呼叫UpdateWindow:
        UpdateWindow (hwnd) ;
       如果显示区域的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT消息呼叫窗口消息处理程序(如果整个显示区域有效,则不呼叫窗口消息处理程序)。这一WM_PAINT消息不进入消息队列,直接由Windows呼叫窗口消息处理程序。窗口消息处理程序完成更新后立即退出,Windows将控制传回给程序中UpdateWindow呼叫之后的叙述。
       您可能注意到,UpdateWindow与WinMain中用来产生第一个WM_PAINT消息的函数相同,最初建立窗口时,整个显示区域内容变为无效,UpdateWindow指示窗口消息处理程序绘制显示区域。

2、绘图闪烁问题
       有时候我们大量绘制屏幕时,可能会出现屏幕闪烁问题,这时候可以采用双缓冲的做法.步骤首先是创建一个内存DC,然后往内存DC中绘图,最后把内存DC的内容复制到显示DC中,完成绘制.具体过程并不复杂,结合代码来说明一下:
  PAINTSTRUCT ps;
  HDC hdc;
  //获取屏幕显示DC  
  hdc = BeginPaint (hWnd, &ps);    
  //创建内存DC
  HDC hdcMem = CreateCompatibleDC(hdc);
  //创建一个bmp内存空间
  HBITMAP hBmp = CreateCompatibleBitmap(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);
  //将bmp内存空间分配给内存DC
  HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
   
  //这是使用者需要绘制的画面,全部往内存DC绘制
  Rectangle(hdcMem,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
  DrawMenuButton(hdcMem);
   
  //将内存DC的内容复制到屏幕显示DC中,完成显示
  BitBlt(hdc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,hdcMem,0,0,SRCCOPY);


  //清除资源
  SelectObject(hdcMem,hOldSel);  
  DeleteDC(hdcMem);  
  EndPaint(hWnd,&ps);

原创粉丝点击