我创建了一个基于对话框的应用程序,对话框的背景是通过BitBlt()函数画上去的,由于窗体上的某些控件要根据不同的状态显示不同的颜色,所以在多次调用Invalidate()函数刷新时就会出现花屏的现象,即背景图片无法显示.
                 请大家不吝赐教!!多谢!!

解决方案 »

  1.   

    对话框的背景是通过BitBlt()函数画上去的
    ==你是在哪个函数中画上去的?
    建议在OnEraseBkgnd(CDC *pDC)绘制背景就可以了。
      

  2.   

    TO:84830388(难的问题请别找我)   我是在OnPaint()函数中画上去的.
       你说的OnEraseBkgnd(CDC *pDC)我在MSDN中查了一下,说是只能在框架窗口中调用,在对话框中无效.
    MSDN:
        Note   This member function is called by the framework to allow your application to handle a Windows message.
      

  3.   

    在OnEraseBkgnd直接返回或者用双缓冲
      

  4.   

    双缓冲显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题。而且多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案。
    MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,只要使用方法得当,再加上一些技巧,用MFC可以得到效率很高的绘图程序。
    我想就我长期(呵呵当然也只有2年多)使用MFC绘图的经验谈谈我的一些观点。1、显示的图形为什么会闪烁?
       我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。例如在OnDraw(CDC *pDC)中这样写:
    pDC->MoveTo(0,0);
    pDC->LineTo(100,100);
    这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见闪烁。其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画只会闪烁一次。这个也可以试验,在OnDraw(CDC *pDC)中这样写:
    for(int i=0;i<100000;i++)
    {
     pDC->MoveTo(0,i);
     pDC->LineTo(1000,i);
    }
    呵呵,程序有点变态,但是能说明问题。
       说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要闪得厉害一些,但是闪烁频率要低。那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,不闪才怪呢。
    2、如何避免闪烁
       在知道图形显示闪烁的原因之后,对症下药就好办了。首先当然是去掉MFC提供的背景绘制过程了。实现的方法很多,
     * 可以在窗口形成时给窗口的注册类的背景刷付NULL
     * 也可以在形成以后修改背景
       static CBrush brush(RGB(255,0,0));
       SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);
     * 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE
       这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,变得一团乱。怎么办?这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有图形进行显示以外,在内存中也有图形在绘制。我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。
    3、如何实现双缓冲
       首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:CDC MemDC; //首先定义一个显示设备对象
    CBitmap MemBitmap;//定义一个位图对象//随后建立与屏幕显示兼容的内存显示设备
    MemDC.CreateCompatibleDC(NULL);
    //这时还不能绘图,因为没有地方画 ^_^
    //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
    MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
     
    //将位图选入到内存显示设备中
    //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
    CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);//先用背景色将位图清除干净,这里我用的是白色作为背景
    //你也可以用自己应该用的颜色
    MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));//绘图
    MemDC.MoveTo(……);
    MemDC.LineTo(……);//将内存中的图拷贝到屏幕上进行显示
    pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);//绘图完成后的清理
    MemBitmap.DeleteObject();
    MemDC.DeleteDC();上面的注释应该很详尽了,废话就不多说了。
    4、如何提高绘图的效率
       我主要做的是电力系统的网络图形的CAD软件,在一个窗口中往往要显示成千上万个电力元件,而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件,可想而知这个过程是非常漫长的。如果加上了图形的浏览功能,鼠标拖动图形滚动时需要进行大量的重绘,速度会慢得让用户将无法忍受。怎么办?只有再研究研究MFC的绘图过程了。
       实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
    如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。
      

  5.   

    在作界面的时候经常碰到一些比较复杂的作图情况,由于作图过于复杂和频繁,所以时常出现闪烁的情况,几经磨练,本人找到一些防止闪烁的方法,写下来和大家共享。1、将Invalidate()替换为InvalidateRect()。    Invalidate()会导致整个窗口的图象重画,需要的时间比较长,而InvalidateRect()仅仅重画Rect区域内的内容,所以所需时间会少一些。虫虫以前很懒,经常为一小块区域的重画就调用Invalidate(),不愿意自己去计算需要重画的Rect,但是事实是,如果你确实需要改善闪烁的情况,计算一个Rect所用的时间比起重画那些不需要重画的内容所需要的时间要少得多。2、禁止系统搽除你的窗口。    系统在需要重画窗口的时候会帮你用指定的背景色来搽除窗口。可是,也许需要重画的区域也许非常小。或者,在你重画这些东西之间还要经过大量的计算才能开始。这个时候你可以禁止系统搽掉原来的图象。直到你已经计算好了所有的数据,自己把那些需要搽掉的部分用背景色覆盖掉(如:dc.FillRect(rect,&brush);rect是需要搽除的区域,brush是带背景色的刷子),再画上新的图形。要禁止系统搽除你的窗口,可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。如BOOL CMyWin::OnEraseBkgnd(CDC* pDC) 
    {
       return TRUE;
       //return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。
    }3、有效的进行搽除。    搽除背景的时候,不要该搽不该搽的地方都搽。比如,你在一个窗口上放了一个很大的Edit框,几乎占了整个窗口,那么你频繁的搽除整个窗口背景将导致Edit不停重画形成剧烈的闪烁。事实上你可以CRgn创建一个需要搽除的区域,只搽除这一部分。如GetClientRect(rectClient);
    rgn1.CreateRectRgnIndirect(rectClient);
    rgn2.CreateRectRgnIndirect(m_rectEdit);
    if(rgn1.CombineRgn(&rgn1,&rgn2,RGN_XOR) == ERROR)//处理后的rgn1只包括了Edit框之外的客户区域,这样,Edit将不会被我的背景覆盖而导致重画。
    {
       ASSERT(FALSE);
       return ;
    }
    brush.CreateSolidBrush(m_clrBackgnd);
    pDC->FillRgn(&rgn1,&brush);
    brush.DeleteObject();注意:在使用这个方法的时候要同时使用方法二。别忘了,到时候又说虫虫的办法不灵。4、使用MemoryDC先在内存里把图画好,再复制到屏幕上。    这对于一次画图过程很长的情况比较管用。毕竟内存操作比较快,而且复制到屏幕又是一次性的,至少不会出现可以明显看出一个东东从左画到右的情况。void CMyWin::OnPaint() 
    {
       CPaintDC dc1(this); // device context for painting
       dcMemory.CreateCompatibleDC(&dc1);
       CBitmap bmp;//这里的Bitmap是必须的,否则当心弄出一个大黑块哦。
       bmp.CreateCompatibleBitmap(&dc1,rectClient.Width(),rectClient.Height());
       dcMemory.SelectObject(&bmp);   //接下来你想怎么画就怎么画吧。
       //dcMemory.FillRect(rectClient,&brush);    dc1.BitBlt(0,0,rectClient.Width(),rectClient.Height(),&dcMemory,0,0,SRCCOPY);
       dcMemory.DeleteDC();
       // Do not call CWnd::OnPaint() for painting messages
    }
      

  6.   

    BOOL CTTTDlg::OnEraseBkgnd(CDC* pDC) 
    {
    HBITMAP hBmp = (HBITMAP)LoadImage(AfxGetInstanceHandle(),"C:\\background.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
    HDC hMemDC = CreateCompatibleDC(pDC->m_hDC);
    CBitmap bmp;
    bmp.Attach(hBmp);
    BITMAP bm;
    bmp.GetBitmap(&bm);
    SelectObject(hMemDC,hBmp);
    CRect rc;
    GetClientRect(rc);
    StretchBlt(pDC->m_hDC,0,0,rc.Width(),rc.Height(),hMemDC,0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
    bmp.DeleteObject();
    return TRUE;
    }
    对话框上正确填充了图片背景
      

  7.   

    TO:rolen(老牛)
       在OnEraseBkgnd直接返回,是什么意思啊
       具体的说一下吗?
      

  8.   

    TO:84830388(难的问题请别找我)
        我是这样做的:
        首先,在头文件里加上一个消息映射afx_msg BOOL OnEraseBkgnd(CDC* pDC);
        然后,在文件中加上你上面的这段代码,在里面设置了一个断点,单步调试时也没有走这个函数                请问我是哪里搞错了?
      

  9.   

    TO:rolen(老牛)   我知道了,多谢
       但为什么这个函数在我这里不起作用呢
       具体该怎么写啊
      

  10.   

    在cpp的消息映射中,需要增加: ON_WM_ERASEBKGND()
      

  11.   

    This member function is called by the framework to allow your application to handle a Windows message.
    此框架非彼框架,不要以为是CFrameWnd。这里指的是MFC框架。
      

  12.   

    但为什么这个函数在我这里不起作用呢+++++++++++++++++++++++++++++++
    这是个消息响应函数,要响应WM_ERASEBKGND消息。
      

  13.   

    TO:Mackz(在相互) 
       
        多谢指点,OnEraseBkgnd(CDC* pDC)这个函数的用法我搞定了,但上面所说的双缓冲的方法还有点晕.
        能举个具体的例子吗