窗口刷新问题(WMPAINT、BeginPaint、EndPaint)

在某些情况下,显示区域的一部分被临时覆盖,Windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功。在以下情况下,Windows可能发送WM_PAINT消息:

  Windows擦除覆盖了部分窗口的对话框或消息框。

  菜单下拉出来,然后被释放。

  显示工具提示消息。

  在某些情况下,Windows总是保存它所覆盖的显示区域,然后恢复它。这些情况是:

  鼠标光标穿越显示区域。

  图标拖过显示区域。

   处理WM_PAINT消息要求程序写作者改变自己向显示器输出的思维方式。程序应该组织成可以保留绘制显示区域需要的所有信息,并且仅当「响应要求」- 即Windows给窗口消息处理程序发送WM_PAINT消息时才进行绘制。如果程序在其它时间需要更新其显示区域,它可以强制Windows产生一个 WM_PAINT消息。这看来似乎是在屏幕上显示内容的一种舍近求远的方法。但您的程序结构可以从中受益。
  1、系统何时发送WM_PAINT消息?

   系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗 口时,等等,这些动作都是由 系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变 的时候,这一般是通过InvalidateRect和 InvalidateRgn函数来完成的。InvalidateRect和InvalidateRgn把指定的区域加到窗口的Update Region中,当应用的消息队列没有其他消息时,如果窗口的Update
Region不为空时,系统就会自动产生WM_PAINT消息。

   系统为什么不在调用Invalidate时发送WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统 把在窗口中的绘制操作当作一种低优先级的操作,于是尽 可能地推后做。不过这样也有利于提高绘制的效率:两个WM_PAINT消息之间通过InvalidateRect和InvaliateRgn使之失效的区 域就会被累加起来,然后在一个WM_PAINT消息中一次得到 更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。像这种通过InvalidateRect和InvalidateRgn来使窗口区域无
效,依赖于系统在合适的时机发送WM_PAINT消息的机 制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以 在无效化窗口区域后利用SendMessage 发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送
WM_PAINT消息而不管Update Region是否为空等。
  2、BeginPaint

   今天在处理WM_PAINT消息时产生了一个低级的错误,并搞的我花了快一个小时才找到原因。我在处理消息时,没有使用BeginPaint和 EndPaint这对函数,结果我其余的消息弹不出来,窗口拖动时,不停闪烁(其实那就是重绘)。后来还是在MSDN上找到了答案,现将原话贴出来。(在 MSDN的The WM_PAINT Message标题中)

   BeginPaint sets the update region of a window to NULL. This clears the region, preventing it fromgenerating subsequent WM_PAINT messages. If an application processes a WM_PAINT message but does not call BeginPaint or otherwise clear the update region, the
application continues to receive WM_PAINT messages as long as the region is not empty. In all cases, an application must clear the update region before returning from the WM_PAINT message.

   BeginPaint函数的作用就是将窗口需要重绘的区域设置为空(也就是Update Region置空)。在正常情况下,我们接收到了WM_PAINT消息后,窗口的Update Region都是非空的(如果为空就不需要发送WM_PAINT消息了)。而当你响应这个消息的时候又不调用BeginPaint来清空,窗口的 Update Region就一直是非空的,系统就会一直发送WM_PAINT消息。这样就形成了一个处理WM_PAINT消息的死循环。这就是我出现错误的原因,低级 错误。

   BeginPaint和WM_PAINT消息紧密相关。试一试在WM_PAINT处理函数中不写BeginPaint会怎样?程序会像进入了一个死循环 一样达到惊人的CPU占用率,你会发现程序总在处理一个接 一个的WM_PAINT消息。这是因为在通常情况下,当应用收到WM_PAINT消息时,窗口的Update Region都是非空的(如果为空就不需要发送WM_PAINT消息了),BeginPaint的一个作用就是把该Update Region置为空,这样如果不调用BeginPaint,窗口的Update Region就一直不为空,如前所述,系统就会一直发送WM_PAINT消息。

   BeginPaint和WM_ERASEBKGND消息也有关系。当窗口的Update Region被标志为需要擦除背景时,BeginPaint会发送WM_ERASEBKGND消息来重画背景,同时在其返回信息里有一个标志表明窗口背景 是否被重画过。当我们用InvalidateRect和InvalidateRgn来把指定区域加到Update Region中时,可以设置该区域是否需要被擦除背景,这样下一个BeginPaint就知道是否需要发送WM_ERASEBKGND消息了。

  当然关于 WM_PAINT消息还有很多的知识需要学习。另外要注意的一点是,BeginPaint只能在WM_PAINT处理函数中使用,并且在调用了BeginPaint函数后,不要忘记了调用EndPaint函数,他们可是一对的。

  3、重画函数 InvalidateRect,UpdateWindow, RedrawWindow的区别

  InvalidateRect是通过线程的消息队列来发送刷新消息,是最常用的。

  UpdateWindow是直接调用窗口函数立即响应刷新消息,使窗口刷新消息优先被响应(消息队列中如果没有WM_PAINT消息就什么都不执行),一般是在ShowWindow之后调用。

  RedrawWindow相当于先调用InvalidateRect,紧接着又调用UpdateWindow,此外RedrawWindow还提供了一些前两者没法做到的功能。

  补充几点:

   1.WM_Paint 是一个被动消息,不能通过普通的方法简单的 sendmessage WM_paint 了事这是不行的;但通过消息由程序员引发不是不可能;通过几个特殊的常数可以做到,不过要到delphi下找sendmessage 可以将消息发送到消息队列;但windows会自动判断是否存在无效的画图区域;如果存在无效的画图区域,则可能会重画,反之则弃用该消息.可以使用 InvalidateRect 等几个APi将屏幕上任意一个个矩形区域设置为无效区域,在UpdateWindow后调用后,windows会自动查找是否存在无效,并重画,该矩形区
域;


BeginPaint和GetDC的区别


这是个windows编程问题。

第一种情况显示出来的字很正常。


  
  
  1. case WM_PAINT:
  2. gdc = BeginPaint (hwnd, &ps);
  3. TextOut (gdc, 0, 0, s, strlen (s));
  4. EndPaint (hwnd, &ps);
break;

第二种情况显示的字不停闪烁。

  
  
  1. case WM_PAINT:
  2. gdc = GetDC (hwnd);
  3. TextOut (gdc, 0, 0, s, strlen (s));
  4. ReleaseDC (hwnd, gdc);
  5. break;
请教两种函数的作用?


BeginPaint() 和EndPaint() 可以删除消息队列中的WM_PAINT消息,并使无效区域有效。

GetDC()和ReleaseDC()并不删除也不能使无效区域有效,因此当程序跳出 WM_PAINT 时 ,无效区域仍然存在。系统就回不断发送WM_PAINT消息,于是程序不断处理WM_PAINT消息。


相 当于BeginPaint、EndPaint会告诉GDI内部,这个窗口需要重画的地方已经重画了,这样WM_PAINT处理完返回给系统后,系统不会再 重发WM_PAINT,而GetDC没有告诉系统这个窗口需要重画的地方已经画过,在你把程序返回给系统后,系统一直以为通知你的重画命令你还没有乖乖的 执行或者执行出错,所以在消息空闲时,它还会不断地发WM_PAINT催促你画,导致程序卡死。


无效区域 :


无效区域就是指需要重画的区域,无效的意思是:当前内容是旧的,过时的。

假设A是新弹出的一个对话框或被激活的现有对话框,A对话框置于原来的活动对话框B前面,造成对话框B的部分或全部被覆盖,当对话框A移开或关闭后,使对话框B原来被覆盖的地方重新可见。那部分被覆盖的地方就称为无效区域。

只 有当一个窗口消息空闲时,系统才会抽空检查一下这个窗口的无效区域是否为非空(WM_PAINT的优先级是最低的。这也就是为什么系统很忙时窗口和桌面往 往会出现变白、刷新不了、留拖拽痕迹等现象的原因),如果非空,系统就发送WM_PAINT。所以一定要用BeginPaint、EndPaint把无效 区域设为空,否则WM_PAINT将一直被发送。


为什么WINDOWS要提出无效区域的概念?


这是为了加速。

因为BeginPaint和EndPaint用到的设备描述符只会在当前的无效区域内绘画,在有效区域内的绘画会自动被过滤,大家都知道,WIN GDI的绘画速度是比较慢的,所以能节省一个象素就节省一个,不用吝啬,这样可以有效加快绘画速度。

可见BeginPaint、EndPaint是比较“被动”的,只在窗口新建时和被摧残时才重画。

而 GetDC用于主动绘制,只要你指到哪,它就打到哪。它不加判断就都画上去,无效区域跟它没关系。对话框没被覆盖没被摧残,它很健康,系统没要求它重画, 但开发者有些情况下需要它主动重画:比如一个定时换外观的窗口,这时候就要在WM_TIMER处理代码用GetDC。这时候再用BeginPaint、 EndPaint的话,会因为无效区域为空,所有绘画操作都将被过滤掉。


eg:

  
  
  1. PAINTSTRUCT ps;
  2. HDC hdc = BeginPaint(hWnd,&ps);
  3. //Create a DC that matches the device
  4. HDC hdcMem = CreateCompatibleDC(hdc);
  5. //Load the bitmap
  6. HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP, 0, 0, 0);
  7. //Select the bitmap into to the compatible device context
  8. HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
  9. //Get the bitmap dimensions from the bitmap
  10. BITMAP bmp;
  11. GetObject(hBmp, sizeof(BITMAP),&bmp);
  12. //Get the window area
  13. RECT rc;
  14. GetClientRect(hWnd,&rc);
  15. //Copy the bitmap image from the memory DC to the screen DC
  16. BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem, 0, 0,SRCCOPY);
  17. //Restore original bitmap selection and destroy the memory DC
  18. SelectObject(hdcMem,hOldSel);
  19. DeleteDC(hdcMem);
  20. EndPaint(hWnd,&ps);
  21. return 0;


/////////////////////////


下面是更加详细的介绍



//

//TITLE:

//     EVC绘制位图–BeginPaint()与GetDC()的区别

//AUTHOR:

//     norains

//DATE:

//     Tuesday   29-August-2006

//

1、BeginPaint()和GetDC()


         在EVC中绘制位图比较方便,有不少现成的函数可供调用,我们所要注意的只是BeginPaint()或GetDC()的使用即可.

         因为代码比较简单,所以不做更多解释.


         这是消息循环函数:

  
  
  1. LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
  2. {
  3. switch(wMsg)
  4. {
  5. case WM_PAINT:
  6. OnPaintMainWnd(hWnd,wMsg,wParam,lParam);
  7. break;
  8. }
  9. return DefWindowProc(hWnd,wMsg,wParam,lParam);
  10. }
       

         响应WM_PAINT消息的函数,在这里进行位图的绘制:

  
  
  1. LRESULT OnPaintMainWnd(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
  2. {
  3. PAINTSTRUCT ps;
  4. HDC hdc = BeginPaint(hWnd,&ps);
  5. //Create a DC that matches the device
  6. HDC hdcMem = CreateCompatibleDC(hdc);
  7. //Load the bitmap
  8. HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP, 0, 0, 0);
  9. //Select the bitmap into to the compatible device context
  10. HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
  11. //Get the bitmap dimensions from the bitmap
  12. BITMAP bmp;
  13. GetObject(hBmp, sizeof(BITMAP),&bmp);
  14. //Get the window area
  15. RECT rc;
  16. GetClientRect(hWnd,&rc);
  17. //Copy the bitmap image from the memory DC to the screen DC
  18. BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem, 0, 0,SRCCOPY);
  19. //Restore original bitmap selection and destroy the memory DC
  20. SelectObject(hdcMem,hOldSel);
  21. DeleteDC(hdcMem);
  22. EndPaint(hWnd,&ps);
  23. return 0;
  24. }


         我们都知道BeginPaint()和EndPaint()需要配套使用,并且这两个函数也只能用在WM_PAINT消息的相应函数当中.如果我们在 WM_PAINT的响应函数中将以上两个绘制函数相应替换为GetDC()和ReleaseDC()会有什么结果呢?

         即:

  
  
  1. HDC hdc = BeginPaint(hWnd,&ps); --> HDC hdc = GetDC(hWnd);
  2. EndPaint(hWnd,&ps); --> ReleaseDC(hWnd,hdc);
       

         编译并运行程序,我们发现窗口一片空白,好像没有绘制位图.但其实不尽然,我们采用单步调试,可以发现其实位图已经绘制出来,只不过又被背景颜色抹掉了. 由此可知,如果需要使用GetDC(),我们对消息循环函数必须要加上对WM_ERASEBKGND的处理:

  
  
  1. LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
  2. {
  3. switch(wMsg)
  4. {
  5. case WM_PAINT:
  6. OnPaintMainWnd(hWnd,wMsg,wParam,lParam);
  7. break;
  8. case WM_ERASEBKGND
  9. return 0;
  10. }
  11. return DefWindowProc(hWnd,wMsg,wParam,lParam);
  12. }


         只要系统不对WM_ERASEBKGND进行默认处理,我们用GetDC()替代BeginPaint()就可以正常使用.

        

         至此我们可以看出BeginPaint(),EndPaint()和GetDC(),ReleaseDC()的区别.前一对只能用在WM_PAINT响应 函数中,并且绘制背景时不会被抹掉;后一对随处可用,但如果用在WM_PAINT响应函数中,那么接下来将会被WM_ERASEBKGND消息的响应函数 的背景绘制给抹掉。

        
2、绘图闪烁问题        

     有时候我们大量绘制屏幕时,可能会出现屏幕闪烁问题,这时候可以采用双缓冲的做法.步骤首先是创建一个内存DC,然后往内存DC中绘图,最后把内存DC的 内容复制到显示DC中,完成绘制.具体过程并不复杂,结合代码来说明一下.

     PS:这段代码也是相应WM_PAINT 消息的.

  
  
  1. PAINTSTRUCT ps;
  2. HDC hdc;
  3. //获取屏幕显示DC
  4. hdc = BeginPaint (hWnd, &ps);
  5. //创建内存DC
  6. HDC hdcMem = CreateCompatibleDC(hdc);
  7. //创建一个bmp内存空间
  8. HBITMAP hBmp = CreateCompatibleBitmap(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);
  9. //将bmp内存空间分配给内存DC
  10. HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
  11. //这是使用者需要绘制的画面,全部往内存DC绘制
  12. Rectangle(hdcMem, 0, 0,SCREEN_WIDTH,SCREEN_HEIGHT);
  13. DrawMenuButton(hdcMem);
  14. //将内存DC的内容复制到屏幕显示DC中,完成显示
  15. BitBlt(hdc, 0, 0,SCREEN_WIDTH,SCREEN_HEIGHT,hdcMem, 0, 0,SRCCOPY);
  16. //清除资源
  17. SelectObject(hdcMem,hOldSel);
  18. DeleteDC(hdcMem);
  19. EndPaint(hWnd,&ps);


OnPaint是WM_PAINT消息的消息处理函数,在OnPaint中调用OnDraw,一般来说,用户自己的绘图代码应放在OnDraw中。


OnPaint()是CWnd的类成员,负责响应WM_PAINT消息。OnDraw()是CVIEW的成员函数, 没有响应消息的功能.当视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows发送WM_PAINT消息。该视图的OnPaint 处理函数通过创建CPaintDC类的DC对象来响应该消息并调用视图的OnDraw成员函数.OnPaint最后也要调用OnDraw,因此一般在 OnDraw函数中进行绘制。


The WM_PAINT message is sent when the UpdateWindow or RedrawWindow member function is called.

在OnPaint中,将调用BeginPaint,用来获得客户区的显示设备环境,并以此调用GDI函数执行绘图操作。在绘图操作完成后,将调用EndPaint以释放显示设备环境。而OnDraw在BeginPaint与EndPaint间被调用。

1) 在mfc结构里OnPaint是CWnd的成员函数. OnDraw是CView的成员函数.
2) OnPaint()调用OnDraw(),OnPrint也会调用OnDraw(),所以OnDraw()是显示和打印的共同操作。

      OnPaint是WM_PAINT消息引发的重绘消息处理函数,在OnPaint中会调用OnDraw来进行绘图。OnPaint中首先构造一个 CPaintDC类得实例,然后一这个实例为参数来调用虚函数OnPrepareDC来进行一些绘制前的一些处理,比设置映射模式,最后调用 OnDraw。而OnDraw和OnPrepareDC不是消息处理函数。所以在不是因为重绘消息所引发的OnPaint导致OnDraw被调用时,比如在OnLButtonDown等消息处理函数中绘图时,要先自己调用OnPrepareDC。
       至于CPaintDC和CClientDC根本是两回事情,CPaintDC是一个设备环境类,在OnPaint中作为参数传递给OnPrepareDC来作设备环境的设置。真正和CClientDC具有可比性的是CWindowDC,他们一个是描述客户区域,一个是描述整个屏幕。
如果是对CVIEW或从CVIEW类派生的窗口绘图时应该用OnDraw。

OnDraw()和OnPaint()有什么区别呢?
首先:我们先要明确CView类派生自CWnd类。而OnPaint()是CWnd的类成员,同时负责响应WM_PAINT消息。OnDraw()是CVIEW的成员函数,并且没有响应消息的功能。这就是为什么你用VC成的程序代码时,在视图类只有OnDraw没有OnPaint的原因(因为继承关系被隐藏了啊!!!)。而在基于对话框的程序中,只有OnPaint。
其次:我们在第《每天跟我学MFC》3的开始部分已经说到了。要想在屏幕上绘图或显示图形,首先需要建立设备环境DC。其实DC是一个数据结构,它包含输出设备(不单指你17寸的纯屏显示器,还包括打印机之类的输出设备)的绘图属性的描述。MFC提供了CPaintDC类和CWindwoDC类来实时的响应,而CPaintDC支持重画。当 视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows 将 WM_PAINT 消息发送给它。该视图的OnPaint 处理函数通过创建 CPaintDC 类的DC对象来响应该消息并调用视图的 OnDraw 成员函数。通常我们不必编写重写的 OnPaint 处理成员函数。

  
  
  1. ///CView默认的标准的重画函数
  2. void CView::OnPaint() //见VIEWCORE.CPP
  3. {
  4. CPaintDC dc(this);
  5. OnPrepareDC(&dc);
  6. OnDraw(&dc); //调用了OnDraw
  7. }
  8. ///CView默认的标准的OnPrint函数
  9. void CView::OnPrint(CDC* pDC, CPrintInfo*)
  10. {
  11. ASSERT_VALID(pDC);
  12. OnDraw(pDC); // Call Draw
  13. }

既然OnPaint最后也要调用OnDraw,因此我们一般会在OnDraw函数中进行绘制。下面是一个典型的程序。
///视图中的绘图代码首先检索指向文档的指针,然后通过DC进行绘图调用。

  
  
  1. void CMyView::OnDraw( CDC* pDC )
  2. {
  3. CMyDoc* pDoc = GetDocument();
  4. CString s = pDoc->GetData();
  5. GetClientRect( &rect ); // Returns a CString CRect rect;
  6. pDC->SetTextAlign( TA_BASELINE | TA_CENTER );
  7. pDC->TextOut( rect.right / 2, rect.bottom / 2, s, s.GetLength() );
  8. }
最后:现在大家明白这哥俩之间的关系了吧。因此我们一般用OnPaint维护窗口的客户区(例如我们的窗口客户区加一个背景图片),用OnDraw维护视图的客户区(例如我们通过鼠标在视图中画图)。(不明白啊???)当然你也可以不按照上面规律来,只要达到目的并且没有问题,怎么干都成。

补充:我们还可以利用Invalidate(),ValidateRgn(),ValidateRect()函数强制的重画窗口,具体的请参考MSDN吧。

        OnDraw中可以绘制用户区域。OnPaint中只是当窗口无效时重绘不会保留CClientDC绘制的内容。

这两个函数有区别也有联系:

1、区别:OnDraw是一个纯虚函数,定义为virtual void OnDraw( CDC* pDC ) = 0; 而OnPaint是一个消息响应函数,它响应了WM_PANIT消息,也是是窗口重绘消息。

2、联系:我们一般在视类中作图的时候,往往不直接响应WM_PANIT消息,而是重载OnDraw纯虚函数,这是 因为在CVIEW类中的WM_PANIT消息响应函数中调用了OnDraw函数,如果在CMYVIEW类中响应了WM_PAINT消息,不显式地调用 OnDraw函数的话,是不会在窗口重绘的时候调用OnDraw函数的。

应用程序中几乎所有的绘图都在视图的OnDraw 成员函数中发生,必须在视图类中重写该成员函数。(鼠标绘图是个特例,这在通过视图解释用户输入中讨论。)


OnDraw 重写:
通过调用您提供的文档成员函数获取数据。
通过调用框架传递给 OnDraw 的设备上下文对象的成员函数来显示数据。
当文档的数据以某种方式更改后,必须重绘视图以反映该更改。默认的 OnUpdate 实现使视图的整个工作区无效。当视图变得无效时,Windows 将 WM_PAINT 消息发送给它。该视图的 OnPaint 处理函数通过创建 CPaintDC 类的设备上下文对象来响应该消息并调用视图的 OnDraw 成员函数。

当没有添加WM_PAINT消息处理时,窗口重绘时,由OnDraw来进行消息响应...(TMD,又不理解了,说错了吧???)当添加WM_PAINT消息处理时,窗口重绘时,WM_PAINT消息被投递,由OnPaint来进行消息响应.这时就不能隐式调用OnDraw了.必须显式调用( CDC *pDC=GetDC(); OnDraw(pDC); )..
隐式调用:当由OnPaint来进行消息响应时,系统自动调用CView::OnDraw(&pDC).


想象一下,窗口显示的内容和打印的内容是差不多的,所以,一般情况下,统一由OnDraw来画。窗口前景需要刷新时,系统会会调用到OnPaint,而OnPaint一般情况下是对DC作一些初始化操作后,调用OnDraw()。


OnEraseBkGnd(),是窗口背景需要刷新时由系统调用的。明显的一个例子是设置窗口的背景颜色(你可以把这放在OnPaint中去做,但是会使产生闪烁的现象)。
至于怎么界定背景和前景,那要具体问题具体分析了,一般情况下,你还是很容易区别的吧。

的确,OnPaint()用来响应WM_PAINT消息,视类的OnPaint()内部根据是打印还是屏幕绘制分别以不同的参数调用OnDraw()虚函数。所以在OnDraw()里你可以区别对待打印和屏幕绘制。
其实,MFC在进行打印前后还做了很多工作,调用了很多虚函数,比如OnPreparePrint()等。
OnInitUpdate是VIEW的初始化
OnUpdate是文档多视时,响应其它视图的改变
OnDraw和OnPaint都是绘图。OnPaint调用OnDraw,并且调用OnPrepareDC
 ---------------------------------------------------------------

一般来说用户的输入/输出基本都是通过视进行,但一些例外的情况下可能需要和框架直接发生作用,而在多视的情况下如何在视之间传递数据。

在使用菜单时大家会发现当一个菜单没有进行映射处理时为禁止状态,在多视的情况下菜单的状态和处理映射是和当前活动视相联系的,这样MFC可以保 证视能正 确的接收到各种消息,但有时候也会产生不便。有一个解决办法就是在框架中对消息进行处理,这样也可以保证当前文档可以通过框架得到当前消息。

在用户进行输入后如何使视的状态得到更新?这个问题在一个文档对应一个视图时是不存在的,但是现在有一个文档对应了两个视图,当在一个视上进行了 输入时如何保证另一个视也得到通知呢?MFC的做法是利用文档来处理的,因为文档管理着当前和它联系的视,由它来通知各个视是最合适的。让我们同时看两个 函数:


  
  
  1. void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
  2. void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL )
当文档的UpdateAllViews被调用时和此文档相关的所有视的OnUpdate都会被调用,而参数lHint和pHint都会被传递。这 样一来发生改变视就可以通知其他的兄弟了。那么还有一个问题:如何在OnUpdate中知道是那个视图发生了改变呢,这就可以利用pHint参数,只要调 用者将this指针赋值给参数就可以了,当然完全可以利用该参数传递更复杂的结构。

视的初始化,当一个文档被打开或是新建一个文档时视图的CView::OnInitialUpdate()会被调用,你可以通过重载该函数对视进行初始化,并在结束前调用父类的OnInitialUpdate,因为这样可以保证OnUpdate会被调用。

文档中内容的清除,当文档被关闭时(比如退出或是新建前上一个文档清除)void CDocument::DeleteContents ()会被调用,你可以通过重载该函数来进行清理工作。

在单文档结构中上面两点尤其重要,因为软件运行文档对象和视对象只会被产生并删除一次。所以应该将上面两点和C++对象构造和构析分清楚。

最后将一下文档模板(DocTemplate)的作用,文档模板分为两类单文档模板和多文档模板,分别由CSingleDocTemplate和 CMultiDocTemplate表示,模板的作用在于记录文档,视,框架之间的对应关系。还有一点就是模板可以记录应用程序可以打开的文件的类型,当 打开文件时会根据文档模板中的信息选择正确的文档和视。模板是一个比较抽想的概念,一般来说是不需要我们直接进行操作的。

当使用者通过视修改了数据时,应该调用GetDocument()->SetModifiedFlag(TRUE)通知文档数据已经被更新,这样在关闭文档时会自动询问用户是否保存数据。

OnDraw()和OnPaint()好象兄弟俩,因为它们的工作类似。

至于不见了的问题简单,因为当你的窗口改变后,会产生无效区域,这个无效的区域需要重画。一般Windows回发送两个消息WM_PAINT(通 知客户区有变化)和WM_NCPAINT(通知非客户区有变化)。非客户区的重画系统自己搞定了,而客户区的重画需要我们自己来完成。这就需要 OnDraw()或OnPaint()来重画窗口。

  
  
  1. void CBaseClasse::OnPaint()
  2. {
  3. CDialog::OnPaint();
  4. }
  5. BOOL CBaseClasse::OnEraseBkgnd(CDC* pDC)
  6. {
  7. // TODO: 在此添加消息处理程序代码和/或调用默认值
  8. CRect rcClient;
  9. GetClientRect(&rcClient);
  10. CDC dc;
  11. LoadPicture(dc);
  12. return TRUE;
  13. }
  14. void CBaseClasse::LoadPicture(CDC& dcMemory)
  15. {
  16. CString strFileName = m_strFileName;
  17. int nPicMode = m_nPicMode;
  18. COLORREF crBg = m_crBg;
  19. CRect rc;
  20. GetClientRect(&rc);
  21. //设置背景色
  22. CBrush brush;
  23. brush.CreateSolidBrush(crBg);
  24. CDC * pDC = GetDC();
  25. HBITMAP hOldBitmap, hOldBmp;
  26. BITMAP bmInfo;
  27. CBitmap MemBitmap, bitMap, *pOldBmp;
  28. //双缓冲绘图
  29. CDC MemDC, oldDC;
  30. MemDC.CreateCompatibleDC( NULL);
  31. MemBitmap.CreateCompatibleBitmap(pDC, rc.Size().cx, rc.Size().cy);
  32. hOldBitmap = (HBITMAP)SelectObject(MemDC.GetSafeHdc(), MemBitmap);
  33. TCHAR curDirectory[ 1024];
  34. CString tempfile;
  35. GetTempPath( 1024 , curDirectory);
  36. if (curDirectory[lstrlen(curDirectory) - 1] == '\\')
  37. tempfile.Format(_T( "%sTemp\\bgpic.bmp"), curDirectory);
  38. else
  39. tempfile.Format(_T( "%s\\Temp\\bgpic.bmp"), curDirectory);
  40. CString file = strFileName;
  41. CImage image;
  42. if (file != _T( ""))
  43. {
  44. file = file.Right(file.GetLength() - file.Find( '.') - 1);
  45. if(file != _T( "bmp"))
  46. {
  47. //加载所在地址的图片
  48. image.Load(strFileName) ;
  49. // 图像另存为BMP格式
  50. image.Save(tempfile,Gdiplus::ImageFormatBMP);
  51. image.Destroy();
  52. }
  53. else
  54. tempfile = strFileName;
  55. hOldBmp = (HBITMAP)LoadImage( NULL, tempfile, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
  56. bitMap.Attach(hOldBmp);
  57. bitMap.GetObject( sizeof(bmInfo), &bmInfo);
  58. oldDC.CreateCompatibleDC( NULL);
  59. pOldBmp = oldDC.SelectObject(&bitMap);
  60. SetStretchBltMode(MemDC.m_hDC, COLORONCOLOR);
  61. //居中效果
  62. {
  63. MemDC.SelectObject(&brush);
  64. MemDC.Rectangle( 0, 0, rc.Width(), rc.Height());
  65. MemDC.BitBlt( 0, 0,rc.Width(),rc.Height(),&oldDC,
  66. (bmInfo.bmWidth-rc.Width())/ 2, (bmInfo.bmHeight-rc.Height())/ 2,SRCCOPY);
  67. }
  68. oldDC.SelectObject(pOldBmp);
  69. bitMap.DeleteObject();
  70. oldDC.DeleteDC();
  71. }
  72. else
  73. {
  74. MemDC.SelectObject(&brush);
  75. MemDC.Rectangle( 0, 0, rc.Width(), rc.Height());
  76. }
  77. if (m_hBitmap)
  78. dcMemory.SelectObject(m_hBitmap);
  79. m_hBitmap = (HBITMAP)dcMemory.SelectObject(SelectObject(MemDC, hOldBitmap));
  80. MemBitmap.DeleteObject();
  81. MemDC.DeleteDC();
  82. ReleaseDC(pDC);
  83. }

猜你喜欢

转载自blog.csdn.net/songsong2017/article/details/83506925