您的位置:首页 > 大数据 > 人工智能

【转】OnDraw,OnPaint,OnEraseBkGnd联系与区别————关于MFC 绘制背景闪烁

2011-11-04 11:02 405 查看
OnDraw&OnPaint

学习中遇到一个问题,OnDraw与OnPaint有什么区别?上网搜索了一下,又查了一下MSDN和MFC的一些源文件,现整理如下。

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

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

TheWM_PAINTmessageissentwhentheUpdateWindoworRedrawWindowmemberfunctioniscalled.

在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处理成员函数。
///CView默认的标准的重画函数
voidCView::OnPaint()//见VIEWCORE.CPP
{

CPaintDCdc(this);
OnPrepareDC(&dc);
OnDraw(&dc);//调用了OnDraw
}
///CView默认的标准的OnPrint函数
voidCView::OnPrint(CDC*pDC,CPrintInfo*)
{
ASSERT_VALID(pDC);
OnDraw(pDC);//CallDraw
}

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

CMyDoc*pDoc=GetDocument();
CStrings=pDoc->GetData();
GetClientRect(&rect);//ReturnsaCStringCRectrect;
pDC->SetTextAlign(TA_BASELINE|TA_CENTER);
pDC->TextOut(rect.right/2,rect.bottom/2,s,s.GetLength());
}
最后:现在大家明白这哥俩之间的关系了吧。因此我们一般用OnPaint维护窗口的客户区(例如我们的窗口客户区加一个背景图片),用OnDraw维护视图的客户区(例如我们通过鼠标在视图中画图)。当然你也可以不按照上面规律来,只要达到目的并且没有问题,怎么干都成。补充:我们还可以利用Invalidate(),ValidateRgn(),ValidateRect()函数强制的重画窗口,具体的请参考MSDN吧。

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

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

1、区别:OnDraw是一个纯虚函数,定义为virtualvoidOnDraw(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来进行消息响应...当添加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()等。

对于OnDraw()
Thismethodiscalledbytheframeworktorenderanimageofthedocument.Theframeworkcallsthismethodtoperformscreendisplay,printing,andprintpreview,anditpassesadifferentdevicecontextineachcase.Thereisnodefaultimplementation.

OnEraseBkGnd&OnPaint



问题是这样产生的.在OnEraseBkGnd中,如果你不调用原来缺省
的OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面,
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd
函数,这时就和窗口缺省的背景刷相关了.缺省的
OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况
下是白刷),而随后你又自己重画背景造成屏幕闪动.
另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你
调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含
调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数
是FALSE,则不会重刷背景.

所以解决方法有三个半:
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数.
2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回.
3.用OnPaint实现,创建窗口时设置背景刷为空
4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一
下,所以不是彻底的解决方法)
都挺简单的.
------------------------------------------------------
在MFC中任何一个window组件的绘图都是放在这两个memberfunction中
在设定上OnEraseBkgnd()是用来画底图的而OnPaint()是用来画主要对象的
举例说明一个按钮是灰色的上面还有文字
则OnEraseBkgnd()所做的事就是把按钮画成灰色
而OnPaint()所做的事就是画上文字

既然这两个memberfunction都是用来画出组件的
那为何还要分OnPaint()与OnEraseBkgnd()呢
其实OnPaint()与OnEraseBkgnd()特性是有差的
1.OnEraseBkgnd()的要求是快速在里面的绘图程序最好是不要太耗时间
因为每当window组件有任何小变动都会马上呼叫OnEraseBkgnd()
2.OnPaint()是只有在程序有空闲的时候才会被呼叫
3.OnEraseBkgnd()是在OnPaint()之前呼叫的
所以OnPaint()被呼叫一次之前可能会呼叫OnEraseBkgnd()好几次

如果我们是一个在做图形化使用者接口的人
常会需要把一张美美的图片设为我们dialog的底图
把绘图的程序代码放在OnPaint()之中可能会常碰到一些问题
比方说拖曳一个窗口在我们做的dialog上面一直移动
则dialog会变成灰色直到动作停止才恢复
这是因为每次需要重绘的时候程序都会马上呼叫OnEraseBkgnd()
OnEraseBkgnd()就把dialog画成灰色
而只有动作停止之后程序才会呼叫OnPaint()这时才会把我们要画的底图贴上去

这个问题的解法比较差点的方法是把OnEraseBkgnd()改写成不做事的function
如下所示
BOOLCMyDlg::OnEraseBkgnd(CDC*pDC)
{
returnTRUE;
}
以上本来是会呼叫CDialog::OnEraseBkgnd()但是如果我们不呼叫的话
程序便不会画上灰色的底色了

比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做
如下所示

//m_bmpBKGND为一CBitmap对象且事先早已加载我们的底图
//底图的大小与我们的窗口client大小一致

BOOLCMyDlg::OnEraseBkgnd(CDC*pDC)
{
CRectrc;
GetUpdateRect(&rc);
CDCsrcDC;
srcDC.CreateCompatibleDC(pDC);
srcDC.SelectObject(m_bmpBKGND);

pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
returnTRUE;
}

特别要注意的是取得重画大小是使用GetUpdateRect()而不是GetClientRect()
如果使用GetClientRect()会把不该重画的地方重画


  http://blog.csdn.net/tzyf333/article/details/6426863
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: