MFC SDI程序中OnDraw,OnPait,OnEraseBkGnd,Invalidate,UpdateWindow

OnDraw :窗口初始输出,
OnCreate :窗口创建初始化期间,
OnPait :窗口重绘。

响应WM_PAINT,On_PRINT和On_PAINT都要调用 On_Draw 。故在OnCreate添加的对话框会在窗口显示前显示。因此一般的图形输出都写在 OnDraw
OnDraw() OnPaint() 好象兄弟俩,因为它们的工作类似。
当窗口改变后,会产生无效区域,这个无效的区域需要重画。一般 Windows 会发送两个消息 WM_PAINT(通知客户区 有变化)和WM_NCPAINT (通知非客户区有变化)。非客户区的重画系统自己搞定了,而客户区的重画需要我们自己来完成。这就需要 OnDraw()  OnPaint() 来重画窗口。
OnDraw()
OnPaint() 有什么区别呢?
首先:我们先要明确 CView 类派生自 CWnd 类。而 OnPaint() CWnd 的类成员,同时负责响应 WM_PAINT 消息。 这就是为什么你用VC成的程序代码时,在视图类只有OnDraw没有OnPaint的原因。而在基于对话框的程序中,只有OnPaintB OnDraw CVIEW 虚函数 ,并且没有响应消息的功能。 CView类响应重画的只有OnPaint函数,只是在函数中调用了虚函数OnDraw而以 在子类中定义OnPaint消息处理函数后要么调用其类的OnPaint或者直接调用OnDraw不然OnDraw函数是不会执行的

其次,要想在屏幕上绘图或显示图形,首先需要建立设备环境 DC 。其实 DC 是一个数据结构,它包含   出设备(不单指你 17 寸的纯屏显示器,还包括打印机之类的输出设备)的绘图属性的描述 MFC 提供了 CPaintDC 类和 CWindwoDC 类来实时的响 应,而 CPaintDC 支持重画。 当视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows  WM_PAINT 消息发送给它。该视图的 OnPaint 处理函数通过创建 CPaintDC 类的DC对象响应该消息并调用视图的 OnDraw 成员函数通常我们不必编写重写的 OnPaint 处理成员函数
///CView默认的标准的重画函数
void CView::OnPaint()
{ 
    CPaintDC dc(this);
    OnPreparDC(&dc);
    OnDraw(&dc); //调用了OnDraw
}
既然OnPaint最后也要调用OnDraw,因此我们一般会在OnDraw函数中进行绘制。下面是一个典型的程序
///视图中的绘图代码首先检索指向文档的指针,然后通过DC进行绘图调用。
void CMyView::OnDraw( CDC* pDC )
{ 
    CMyDoc* pDoc = GetDocument(); 
    CString s = pDoc->GetData(); // Returns a CString
    CRect rect;
    GetClientRect( &rect ); 
    pDC->SetTextAlign( TA_BASELINE | TA_CENTER ); 
    pDC->TextOut( rect.right / 2, rect.bottom / 2, s, s.GetLength() );
}
最后:现在大家明白这哥俩之间的关系了吧。因此我们一般用OnPaint维护窗口的客户区(例如我们的窗口客户区加一个背景图片),用OnDraw维护视图的客户区(例如我们通过鼠标在视图中画图)。当然你也可以不按照上面规律来,只要达到目的并且没有问题,怎么干都成。


OnPaint()与OnDraw的区别:

1、Invalidate()和InvalidateRect()其实是触发对onPaint()函数的调用,   
OnPaint()函数调用OnDraw()函数,   
OnDraw函数还需要同时支持打印机输出。OnPaint()函数和OnPrint()函数都要调用OnDraw(),于是同样的绘图代码既可以用于屏幕输出也可以用于打印机输出   
在编程中,一般重载OnDraw()就可以了。如果定义了OnPaint()函数,并且在OnDraw()里面有要显示的内容,那么需要显示的调用OnDraw(),即OnDraw(&dc)。

2、OnDraw()是被OnPaint()调用的虚函数,在CView中定义为纯虚函数,因此必须被重载,其设备上下文由OnPaint()提供。   
OnPaint()是响应消息WM_PAINT的响应函数,默认实现中在,先调用BeginPaint(),最后调用EndPaint,OnDraw在BeginPaint与EndPaint间被调用。   

建议编程时使用OnDraw。 

3、CView封装了两个函数,OnPaint()与OnPrint(),分别对应WM_PAINT与WM_PRINT。MFC为了提供更标准简易的编程接口,所以又提供了OnDraw()这个函数。OnDraw()将被OnPaint()或OnPrint()调用,根据二者分别传进来的不同DC(Paint DC或Print DC),从而完成屏幕绘制或打印工作,而不需再为两种情况分别写代码。当然,如果你只关心屏幕绘制工作,而不关心打印问题,那你完全可以直接重载OnPaint()完成绘制,而不使用OnDraw()。



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组件的绘图 都是放在这两个member function中, 在设定上,OnEraseBkgnd()是用来画底图的,而OnPaint()是用来画主要对象的。
举例说明,一个按钮是灰色的,上面还有文字, 则OnEraseBkgnd()所做的事就是把按钮画成灰色, 而OnPaint()所做的事,就是画上文字。
 
既然这两个member function都是用来画出组件的, 那为何还要分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
如下所示
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
    return TRUE;
}
以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话, 程序便不会画上灰色的底色了。
 
比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做(注意:背景图是推荐这么做的,但是如果频繁计算耗费时间,则不合适)
如下所示
// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图
// 底图的大小与我们的窗口client大小一致
 
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetUpdateRect(&rc);
CDC srcDC;
srcDC.CreateCompatibleDC(pDC);
srcDC.SelectObject(m_bmpBKGND);
 
pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
return TRUE;
}
特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect()
如果使用GetClientRect() 会把不该重画的地方重画


Invalidate介绍
  void Invalidate( BOOL bErase = TRUE ); 
  该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘,例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。参数bErase为TRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。
与UpdateWindow( )的区别
UpdateWindow( )的作用是使窗口立即重绘。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。调用UpdateWindow函数可使WM_PAINT被直接发送到目标窗口,从而导致窗口立即重绘
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值