一.基本概念

CClientDC, CPaintDC都是从CDC派生出来的类。

两者的区别有:

1.ClientDC的构造函数执行执行GetDC, 析构函数执行ReleaseDC;CPaintDC的构造函数执行BeginPaint  析构函数执行EndPaint 

2.CClientDC不会使无效矩形区域变为有效,而CPaintDC会无效矩形变为有效。

如下代码:

 

void CDemoDlg::OnPaint()
 {
   CClientDC dc(this);
  dc.Ellipse(12, 12, 100, 100);
  
 }

执行结果可以看出椭圆在对话框中不断的闪动,这是为什么啦?

这个就要从无效区域说起了,当我们在OnPanit进行绘图时(上面那段代码没有任何一段发送WM_PAINT消息的代码)

因为对话框在创建的时候要调用UpdateWindow强制发送一个WM_PAINT给dlg的窗口过程,窗口过程调用OnPaint进行绘制,CClientDC由于不会使无效区域变为有效,相当于窗口过程进了消息死循环,不停的发送WM_PAINT消息,而由于背景的绘制和前景的绘制导致椭圆会不停的闪烁.

解决方法:

(1)把 CClientDC 改为CPaintDC

(2)或者改为:

void CDemoDlg::OnPaint()
 {
   CRect rect;
  GetClientRect(&rect);
  ValidateRect(rect); //强制把无效矩形区域变为有效
  CClientDC dc(this);
  dc.Ellipse(12, 12, 100, 100);
  }

 

3.重点理解WM_PAINT 和WM_ERASEBKGND

(1)WM_PAINT 消息是由操作系统发送的,用来绘制前景,当窗口有无效区域的时候,系统会给窗口的消息队列投递一个WM_PAINT (注意:只有当消息队列里面队列为空且窗口的无效区域不为空的时候系统才会投递一个消息队列),继而会调用OnPaint进行绘制,此消息不能send,只能post。

还有一个需要注意的是:UpdateWindow会强制直接的给窗口过程,进而直接调用OnPaint,会绕过消息队列

(2)WM_ERASEBKGND一般是由DefWindowProc用窗口类定义的 hbrBackground 来进行擦出背景的, 如果窗口类WNDCLASS 里面的hbrBackground 为NULL的话,应用程序需要重载OnEraseBackgroud进行背景的擦除,如果返回0的话表示窗口背景仍然需要删除,否则不删除,背景的删除是否也可以用我们下文将要讲到的两个函数来制定

 

4.理解Invalidate和InvalidateRect以及响应的validate版本

Invalidate和InvalidateRect都不会给应用程序的消息队列投递WM_PAINT  消息,Invalidate是使整个客户区无效,而InvalidateRect会把指定的无效区域的矩形加到Cwnd里面的无效矩形区域里面去并计算新的无效区域的大小,两者的bErase参数指明窗口的背景是否被删除

二.造成闪烁的原因:

我们的绘图过程大多放在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);
}

呵呵,程序有点变态,但是能说明问题。

 

说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要闪得厉害一些,但是闪烁频率要低。那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,不闪才怪呢。

 

三。解决闪烁的方法

1、调用InvalidateRect指定某个矩形区域进行绘制,而不调用Invalidate函数指定需要重画的地方,调用完后直接调用UpdateWindow()

2. 在形成窗口的时候把hbrBackground设置为NULL,重载OnEraseBackgroud返回TRUE;

3.用双缓冲绘图