MFC中窗口刷新函数详解

Published

一、CView中OnDraw()函数调用的时间

视图绘画机理 在VC++的文档、视结构中, CView的OnDraw函数用于实现绝大部分图形绘制的工作。 如果用户改变窗口尺寸,或者显示隐藏的区域, OnDraw函数都将被调用来重画窗口。并且, 当程序文档中的数据发生改变时, 一般必须通过调用视图的Invalidate(或InvalidateRect) 成员函数来通知Windows所发生的改变, 对Invalidate的调用也会触发对OnDraw函数的调用。 正因为OnDraw函数被频繁调用,所以在其执行时, 每次都刷新填充一次视图客户区域,便会使屏幕不稳定 ,产生闪烁现象。 其实在程序调用OnDraw函数之前,会触发一个Windows消息: WM_ERASEBKGND,以擦除视图刷新区域。在缺省情况下, Windows系统使用视图窗口注册时窗口类中的成员hbrBackground 所描述的画刷来擦除屏幕,这一般会将屏幕刷新成COLOR_WINDOW 所对应的颜色。因此,在OnDraw函数中设置背景颜色的执行 过程是这样的:先将屏幕刷新成COLOR_WINDOW所对应的颜色, 接着又在OnDraw函数中填充其他颜色, 这正是产生屏幕闪烁的根本原因;

二、执行原理

调用Invalidate后,执行的OnUpdate()函数重绘窗口。

CView::OnInitialUpdate函数:应用程序被启动时,或从“文件”菜单中选择了“新建”或“打开”时,CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint = 0, pHint = NULL)的OnUpdate函数之外,没做其他任何事情。可以重载此函数对文档所需信息进行初始化操作。

CView::OnUpdate函数:应用程序调用了CDocument::UpdateAllViews函数时,应用程序框架就会相应地调用该函数。

 

按引:Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区。而UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最多只有一条)的无效区。效果很明显,调用Invalidate之后,屏幕不一定马上更新,因为WM_PAINT消息不一定在队列头部,而调用UpdateWindow会使WM_PAINT消息马上执行的,绕过了消息队列。如果你调用Invalidate之后想马上更新屏幕,那就加上UpdateWindow()这条语句。

 

UpdateData():

    当你使用了ClassWizard建立了控件和变量之间的联系后:当你修改了变量的值,而希望对话框控件更新显示,就应该在修改变量后调用UpdateData(FALSE);如果你希望知道用户在对话框中到底输入了什么,就应该在访问变量前调用UpdateData(TRUE),将控件的输入映射到变量中。

Invalidate():
     该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘。例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。参数bErase为TRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。

InvalidateRect():
    该函数的功能与Invalidate基本一样,不同的是,它是使指定的某个区域无效,需要输入一个区域,如果参数为NULL,则设置整个窗口为无效区。

UpdateWindow():
     UpdateWindow( )的作用是使窗口立即重绘。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。调用UpdateWindow函数可使WM_PAINT被直接发送到目标窗口,从而导致窗口立即重绘。

UpdateWindow:如果有无效区,则马上sending a WM_PAINT message到窗口处理过程,不进消息队列进行排队等待,立即刷新窗口,否则,什么都不做。 
InvalidateRect:设置无效区,如果为NULL参数,则设置整个窗口为无效区。当应用程序的那个窗口的消息队列为空时,则sending a WM_PAINT message(即使更新区域为空).在sending a WM_PAINT message的所有InvalidateRect的更新区域会累加。

1:设置无效区 
InvalidateRect 

2:立即刷新 
UpdateWindow() 

如果不调用 InvalidateRect就调用 UpdateWindow,那么UpdateWindow什么都不做。 如果调用 InvalidateRect 后不调用UpdateWindow,则系统会自动在窗口消息队列为空的时候,系统自动发送一WM_PAINT消息。 


调用UpdateWindow()时将会发送一个WM_PAINT消息,而应用程序在接收到WM_PAINT消息后,将自动地调用Invalidate()。所以,在程序代码中,不一定要出现Invalidate()!

UpdateWindow()就是立即发送WM_PAINT消息,updateWindow要求系统对区域进行立即重绘,其只对声明为无效的区域起作用,而Invalidate()是声明无效区域的方式之一。

Invalidate()表示客户区域无效,在下次WM_PAINT发生时重绘。而WM_PAINT是由系统进行维护的,每当CWnd的更新区域不为空,并且在应用程序的窗口消息队列中没有其它消息时,Windows就发送一条WM_PAINT消息。

Invalidat最后也是调用InvalidatRect。

RedrawWindow 强制刷新,会调用WM_PAINT,但如果你强制刷新的部分不存在就不会调用WM_PAINT。若不带任何参数,则本窗口全部刷新。

*****************************************************************************************************************************************

看到有人在网上提出问题,他在Invalidate后面又写了绘图的函数但是没有执行,这是因为invalidate执行过以后就转到PAINT命令了,所以后面的都没有显示。

也终于想通我绘的图一直在闪啊闪,这是因为我在PAINT里面用到了Invalidate()函数,所以他不停的自嵌套,导致绘的图在不停的闪。

总之:Invalidate让客户区处于可以重画的状态,而UpdateWindow开始重画,但是它首先需判断客户区是否为空,不空则UpdateWindow不执行,为空才执行重画。

*********************************************************************************************************************************************

在刷新窗口时经常要调用重绘函数MFC提供了三个函数用于窗口重绘
     InvalidateRect(&Rect)
    Invalidate()
     UpdateWindow()
       当需要更新或者重绘窗口时,一般系统会发出两个消息WM_PAINT(通知客户区有变化)和WM_NCPAINT(通知非客户区有变化)WM_NVPAINT系统会自己搞定WM_PAINT消息对应的函数是OnPaint(),它是系统默认的接受WM_PAINT消息的函数,但我们一般在程序中做重绘时都在OnDraw函数中进行的,因为在视图类ONPAINT函数中调用了ONDRAW函数。
       CView默认的标准的重画函数
          void CView::OnPaint()
             {  
                   CPaintDC dc(this);   
                   OnPreparDC(&dc);  
                   OnDraw(&dc); //调用了OnDraw

            } 
        上面讲到InvalidateRect(&Rect) 和 Invalidate()。两个函数形式和功能差不多,但Invalidate是使得整个窗口形成无效矩形,而InvalidateRect(&Rect)是使得指定的区域无效。Invalidate()申明无效,等待WM_PAINT消息以便重绘,队列中无其他消息时系统会自动发送。而UpdateWindow()会立即发送WM_PAINT,不过在它发送前,先调用GetUpdateRect(hWnd,NULL,TRUE)看有无可绘制区域,如果没有则不发送消息。RedrawWindow()RedrawWindow()则是具有Invalidate()和UpdateWindow()的双特性。声明窗口的状态为无效,并立即更新窗口,立即调用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是否为空等。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消息了。另外要注意的一点是,BeginPaint只能在WM_PAINT处理函数中使用

 


OnDraw,一般是收到WM_PAINT消息时调用,所以应用程序一般通过Invalidate产生WM_PAINT消息来间接调用OnDraw。当窗体无效等情况下,window也会产生WM_PAINT消息,这时OnDraw 也被间接调用。 
OnUpdate 是CView提供的一个方法,一般当文档修改时调用,应用程序框架在CView::OnInitialUpdate 和CDocument::UpdateAllViews 的默认实现中都会调用 
OnUpdate,OnUpdate的默认实现是通过Invalidate产生WM_PAINT,这时OnDraw又被调用了。 
OnDraw除了你和应用程序框架间接调用外,window还可能间接调用它。 
OnUpdate一般只有你的程序和应用程序框架会调用的。当然它的默认实现你可以改变的
**********************************************************************************************************************
OnInitUpdate是VIEW的初始化 
OnUpdate是文档多视时,响应其它视图的改变 


 
OnDraw和OnPaint都是绘图。OnPaint调用OnDraw,并且调用OnPrepareDC