OnPaint()与OnDraw()的区别:
OnPaint是WM_PAINT的消息响应函数,在MFC的基类里OnPaint函数调用了OnDraw()函数。
OnPaint函数另外还调用了OnPrepareDC()函数。
如果在窗口子类覆盖了OnPaint函数,当MFC调用我们重写的OnPaint函数时,就调不到OnDraw()函数了,
除非我们去调用OnDraw()函数。
Invalidate函数族介绍:
函数: Invalidate(BOOL bErase = TRUE)
函数: InvalidateRect(CRect* rect,BOOL bErase = TRUE)
函数: InvalidateRgn(CRgn* rgn,BOOL bErase = TRUE)
将一个区域放入Update Region中。[Update
Region]是窗口的无效区域。[无效区域]是需要重绘的区域。
为何需要重绘呢?
第1类事件:当需要展现某窗口的“新的”区域时,就需要重绘。
当创建一个窗口时;当把窗口从另一个窗口的背后弹到前面时;当从图标化到最大化转变时;
当滚动式的窗口,发生滚动事件时;当把遮挡在前面的窗口一点一点拖开,
让被遮挡的窗口一点一点的露出时
就需要重绘。这些动作都是由WINDOWS系统管理的,系统会很肯定的认为,在上述事件发生时,必须重绘。
注:如果把被遮挡的窗口,一点一点的遮盖住,就不需要重绘。
第2类事件:当有业务数据改变的事件发生时。
窗口是用来显示业务数据的。比如我的窗口正在显示一个椭圆,后台将业务数据变成
了三角形,我需要显示这个三角形,这时就需要重绘了。对于第2类事件,
WINDOWS不可能感知到你需要重绘。
例如:我有一个变量 int m_shape=1; 1代表椭圆,2代表三角形。
我需要让窗口的图形显示m_shape代表的形状
当我把m_shape的值由1变成2时,WINDOWS根本不知道我需要重绘一个三角形。
对于第1类事件,WINDOWS会自动发出WM_PAINT消息,窗口的对应处理函数OnPaint()就会被调用。
程序员不必关心“在何时”和“在何地”重绘。对于第2类事件,程序员必须通知WINDOWS,在何地重绘。
至于“何时”重绘,WINDOWS会挑选一个合适的时机。
Invalidate函数族同第2类事件有关。通过调用Invalidate函数族,通知windows系统,
我有一些窗口区域需要重绘。CWnd::Invalidate()是说整个窗口都需要重绘。
CWnd::InvalidateRect()是说窗口的某个矩形区域需要
重绘。CWnd::InvalidateRgn是说窗口的某个不规则区域需要重绘。
“不规则区域”可以是任意多边形,椭圆形,当然也包括矩形。
用伪代码说明上述三个函数的等价关系。
CRgn rgn;
rgn.CreateRectRgn(...);
CWnd::InvalidateRgn(&rgn,...);
等价于
CRect rect;
CWnd::InvalidateRect(&rect,...);
CRect rect;
GetClientRect(&rect);
CWnd::InvalidateRect(&rect,...);
等价于
CWnd::Invalidate(...);
Invalidate函数族中,都有一个bErase参数。此参数的含义:bErase=TRUE擦除背景,
bErase=FALSE不擦除背景
何为背景:想象窗口就是小朋友的画纸。当你把这张画纸给另一个小朋友画画时,
前一个小朋友画的东西就是
“背景”。一般我们不希望两个小朋友画的东西夹杂在一起。我们就需要擦除前一个小朋友画的“背景”。
用什么擦除背景呢?WINDOWS允许我们设置“背景刷”,就是用某种颜色的刷子把整个画纸涂抹一遍,
有点像刷白墙。
Invalidate函数族的调用不会立刻引发窗口重绘。Invalidate函数族只是累积和标记需要重绘的区域。
下一次"WM_PAINT message
occurs"时(MSDN语),一次性处理累积和标记的所有需要重绘的区域。显然从
Invalidate调用,到实际的重绘动作是异步调用的。人类视觉有延迟现象,一秒连续播放24帧就可以认为是
“动画”了,所以上述重绘方式人类是察觉不出异样的。假设每次Invalidate都同步的引发重绘OnPaint,有两个不良后果:一是程序效率太差,二是可能让人察觉出闪烁感。
那么何时下一次"WM_PAINT message occurs"呢?
当应用的消息队列没有其他消息时,并且窗口的[Update Region]不为空时,
系统就会自动产生WM_PAINT消息。
例子:演示“失效区域”是如何起作用的。
//每次重绘,会交替展现两个不同的椭圆形。
void XXX::OnPaint()
{
CPaintDC dc(this);
static
int x=0;
if
(x==0){
dc.Ellipse (0, 0, 100, 200);
//横向的椭圆形
x=1;
}
else{
dc.Ellipse (0, 0, 200, 100);
//竖向的椭圆形
x=0;
}
}
某CButton中,OnBnClicked伪代码:
CRect rect;
XXX->GetClientRect(&rect);
rect.bottom = rect.bottom/2;
XXX->InvalidateRect(&rect,true);
//擦除背景
即使我擦除了背景,仍旧能看到前一个椭圆。因为我设定的“非法区域”只是rect的上半部分。
CRect rect;
XXX->GetClientRect(&rect);
XXX->InvalidateRect(&rect,true);
//擦除背景
可以正常的展现,能交替展现两个不同的椭圆形。
Validate函数族:
作用同Invalidate函数族相反,将一个区域从[Update Region]排除,这样就不会被重绘。
当然了,Validate要在下一次"WM_PAINT message occurs"之前的调用才能起作用。
如果发生了第1类事件,会造成大面积的区域变成“需重绘区域”,Validate设定的“不需重绘区域”
又会变成“需重绘区域了”。
UpdateWindow函数:
UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息。
UpdateWindow可以绕开应用程序消息循环,直接发送WM_PAINT消息给窗口。
RedrawWindows函数:
可以简单理解为Invlidate + UpdateWindow,但是功能更强大一些。
SetRedraw函数:
可以阻止窗口重绘。是解决窗口闪烁的一个办法
MSDN的一个例子:
m_List.SetRedraw(FALSE); //暂时阻止窗口m_List重绘
...//大规模对m_List改头换面
m_List.SetRedraw(TRUE);
//解除阻止窗口m_List重绘
m_List.Invalidate();
m_List.UpdateWindow(); //触发WM_PAINT消息
SetRedraw函数好像是戏台的前幕,后面切换场景时,先遮挡一下。
<>介绍了图形密集型程序“闪”的原因。
主要技术为:1 选用黑色背景或者背景同前景相近的颜色,作为背景刷。
2 双缓冲技术,就是先在内存设备DC里准备好需要显示的内容,然后拷贝到屏幕设备DC
3 剪裁区域的合理利用。