函数功能:BeginPaint 函数为指定窗口进行绘画作准备,并用将和绘画有关的信息填充到一个 PAINTSTRUCT 结构中。
API 函数原型:HDC BeginPaint( In HWND hwnd, Out LPPAINTSTRUCT lpPaint);
参数含义
hwnd需要重新绘制的窗口句柄
lpPaint指向 PAINTSTRUCT 结构的指针,用于存放绘画相关的信息
返回值:1. 如果函数成功,返回值是指定窗口的“显示设备描述表”句柄;2. 如果函数失败,返回值是 NULL,表明没有得到显示设备的内容。
机制解析:
1.在WM_PAINT消息中调用 BeginPaint()方法,BeginPaint方法返回一个当前无效区域的HDC句柄,并把该无效区域设置为有效区域。所谓无效区域就是需要应用程序重新绘制的区域,反之则为有效区域。BeginPaint被调用时同时返回了一个PAINTSTRUCT结构,其中给出了本次重绘的Clip Rectangle,所有在这个rectangle之外的绘制操作都不会显示。值得注意的是Petzold在砖头里面反复强调在WM_PAINT消息处理函数中BeginPaint方法需要和EndPaint方法配对使用,还有BeginPaint中返回的HDC不能缓存起来使用。【因为每次在WM_PAINT消息响应中为了绘制而需要取得的无效区域是不固定的!】
2.BeginPaint/EndPaint()。而这两个函数的作用,是获取DC、剪裁区域和从消息队列中移走WM_PAINT消息(因此,如果不调用一遍BeginPaint和EndPaint的话,WM_PAINT消息就不会从消息队列里面移走,系统就会不停的发送WM_PAINT,这就是问题中所提到的不停的调用OnPaint函数的原因了)。这两个函数,必须在WM_PAINT消息中调用,而且只能调用一次! 【每一次绘制必须BeginPaint/EndPaint()配对使用;且只能调用一次=>即不能连续调用BeginPaint关联同一个窗口,否则测试好像是有一个返回的dc绘制了也是无效果的】
无效区域与更新:
- BeginPaint 函数自动设置显示设备内容的剪切区域,而排除任何更新区域外的区域。该更新区域可以通过 InvalidateRect 或 InvalidateRgn 函数设置,也可以是系统在改变大小、移动、创建、滚动后设置的,或者其他的影响客户区的操作来设置的。
- 如果更新区域被标记为可擦除的,BeginPaint 发送一个 WM_ERASEBKGND 消息给窗口。
- 一个应用程序除了响应 WM_PAINT 消息外,不应该调用 BeginPaint。
- 每次调用 BeginPaint 都应该有相应的 EndPaint 函数。
- 如果被绘画的客户区中有一个 caret(caret:插入符。是窗口客户区中的一个闪烁的线,块,或位图。插入符通常表示文本或图形将被插入的地方。即一闪一闪的光标),BeginPaint 自动隐藏该符号,而保证它不被擦除。
- 如果窗口类有一个背景刷,BeginPaint 使用这个刷子来擦除更新区域的背景。
推论:调用基类的OnPaint函数后在子类中的绘制代码失效!
CDialog::OnPaint(); //处理WM_PAINT消息并将消息结构中无效区域变为有效。
CPaintDC dc(this);//此时因为窗口区域都是有效区域,不会重绘
dc.Ellipse(0,0,500,500)//只在无效区域绘图,因此时窗口区域为有效状态,所以不重绘,圆形不可见
解决方法:当我们既要利用基类的OnPaint中绘制一些总体的风格,又想在每一个子类中可以控制绘制的其他界面元素的时候可以利用模板方法模式在 基类中提供一个默认实现的虚函数如OnDraw然后在适当位置的基类OnPaint中调用这个虚函数;子类中根据需要去重写这个虚函数【参考设计模式-模板方法模式】