深度分析WM_PAINT和WM_ERASEBKGND消息
做windows开发这么久了,一直以来对WM_PAINT和WM_ERASEBKGND消息总是感觉理解的不准确,每次要自绘一个窗口都因为知其然不知其所以然,偶然发现一篇文章,详细透彻地分了这个两个消息的用途和设计初衷,这篇文章也是我见过最深入也是最准确关于WM_PAINT和WM_ERASEBKGND消息的,文中每一句话都值得咀嚼。先转载如下:
一直以来,对于WM_PAINT和WM_ERASEBKGND消息不是很清楚,从书上和网上找了很多资料,大体上有以下几点说法:
1>WM_PAINT先产生,WM_ERASEBKGND后产生
2.WM_PAINT产生后,在调用BeginPaint时
hdc = BeginPaint(hWnd, &ps);
如果ps.fErase为true,则BeginPaint会产生WM_ERASEBKGND消息
3.BeginPaint函数用来擦除窗口背景
4.WM_ERASEBKGND用来绘制背景
经过调试、分析,发现上面的说法并不正确。以下是一些测试代码,代码后面附上一些分析。最后总结出几点,可以解释程序中出现的所有关于窗口重绘的问题。
如有不正确的地方,大家可以指正。
为了说明问题,在此不说WM_NCPAINT消息(非客户区消息),只说WM_ERASEBKGND消息和客户区的WM_PAINT消息
//此段代码摘自vc6应用程序向导自动生成的代码,并添加了一些测试代码
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_ERASEBKGND: //如果处理了这个消息,则默认消息处理函数不会调用,背景就不会绘制
{
static int iCount=0;
char ch[MAX_PATH];
sprintf(ch,"%d ---------WM_ERASEBKGND\n",iCount); //这个函数需要包含#include<stdio.h>
OutputDebugString(ch); //调试时便于观察
iCount++;
break;
}
case WM_PAINT:
{
OutputDebugString(" -------------WM_PAINT\n");
hdc = BeginPaint(hWnd, &ps); //使无效区域变得有效,并填充ps结构
// TODO: Add any drawing code here...
//绘制一个蓝色椭圆,ps.rcPaint保存了客户区矩形
HBRUSH hbrush=::CreateSolidBrush(RGB(0,0,255));
::SelectObject(hdc,hbrush);
::Ellipse(hdc,ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
::DeleteObject(hbrush);
EndPaint(hWnd, &ps);
break;
}
case WM_LBUTTONDOWN: //调用DefWindowProc擦除客户区背景
{
HDC hdc;
hdc=::GetDC(hWnd);
WPARAM w=(WPARAM)hdc;
LPARAM l=0;
DefWindowProc(hWnd, WM_ERASEBKGND, w, l);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
先说一下程序运行时发现的一些现象:
1.
上面的代码:如果添加了WM_ERASEBKGND消息,里面什么也不做,如下
case WM_ERASEBKGND:
break;
则当程序运行时,如果收到WM_ERASEBKGND消息,则这个switch-case结构中就不会执行默认消息处理函数DefWindowProc,运行时发现,窗口的背景就没有了,即背景为空。
这说明了窗口背景仅仅是由默认的消息处理函数DefWindowProc绘制的。
(注:注册窗口类时,背景设置为白色wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);)
2.
如上面的代码,因为有WM_ERASEBKGND消息,则程序运行时窗口背景为NULL,
但是如果添加了WM_LBUTTONDOWN消息,从里面调用默认窗口消息处理函数,如下
case WM_LBUTTONDOWN:
{
HDC hdc;
hdc=::GetDC(hWnd);
WPARAM w=(WPARAM)hdc; //变量w作为WM_ERASEBKGND消息的wParam参数,保存了设备环境句柄
LPARAM l=0;
DefWindowProc(hWnd, WM_ERASEBKGND, w, l); //调用默认消息处理函数DefWindowProc
}
break;
程序运行时,如果用鼠标单击一下窗口客户区,则窗口的背景就会显示!这进一步说明了窗口的背景色是由默认消息处理函数DefWindowProc绘制的。
3.
以上代码,因为添加了WM_ERASEBKGND消息,所以窗口背景是空。
虽然在WM_PAINT消息中有
hdc = BeginPaint(hWnd, &ps);函数的调用,但是窗口背景仍然是空,这说明了BeginPaint函数并不会擦除背景(即用默认画刷绘制窗口背景)。
BeginPaint函数只做了两件事情:
1》使窗口无效区域变得有效,从而使Windows不再发送WM_PAINT消息(直到窗口大小改变等,使窗口再次变得无效)。
(如果窗口一直无效,则Windows会不停地发送WM_PAINT消息)
1》填充PAINTSTRUCT结构。填充这个结构的目的,是让程序员可以根据ps变量中的标志值进行某些操作
4.
调试的时候,发现:当窗口改变大小,或者其它操作使窗口变得无效时,WM_ERASEBKGND消息总是先于WM_PAINT消息发出,而且如果产生WM_ERASEBKGND消息,
则最后一个WM_ERASEBKGND的下一条消息一定是WM_PAINT消息(WM_ERASEBKGND可能会连续产生几次)。WM_ERASEBKGND消息和WM_PAINT消息之间没有其它消息
--------------------------------------------
以下是一些总结
1.窗口背景的擦除(即绘制)
窗口的背景色是由默认的消息处理函数DefWindowProc擦除的(即这个函数使用注册窗口类时使用的背景刷擦除窗口背景)。
什么时候绘制?在窗口函数收到WM_ERASEBKGND消息,DefWindowProc函数以WM_ERASEBKGND为参数,才会绘制窗口背景
(注:当WM_ERASEBKGND消息产生后,窗口一定有一部分变得无效)
2.窗口的无效:
当拖动窗口的一个顶点改变了窗口的大小、窗口由最小化恢复到最大化、窗口的一部分被其它窗口遮住又重新显示、调用MoveWindow函数改变了窗口大小、窗口移动到桌面之外的
部分被拖回重新显示时,窗口就会变得无效。 无效区域是整个客户区,因此默认窗口处理函数DefWindowProc会擦除整个客户区。
(注:拖动窗口标题栏移动窗口,只要窗口没有移动到屏幕之外,那么这两个消息都不产生)
当窗口无效时,Windows会给窗口发出WM_ERASEBKGND消息和WM_PAINT消息,而且WM_ERASEBKGND先发出一次或者几次,紧接着是WM_PAINT
例外:InvalidateRect函数的调用会使窗口变得无效,并产生WM_ERASEBKGND消息和WM_PAINT消息,而WM_ERASEBKGND是否产生取决于参数bErase
void InvalidateRect (
LPCRECT lpRect,
BOOL bErase = TRUE );
当参数bErase为true时,WM_ERASEBKGND消息产生,当bErase为false时WM_ERASEBKGND消息不产生
3.消息的处理过程
当窗口无效时,
先发出WM_ERASEBKGND消息若干次-----------再发出WM_PAINT消息,WM_ERASEBKGND和WM_PAINT之间没有其它消息
WM_ERASEBKGND消息的后面一定是WM_PAINT
1》WM_ERASEBKGND消息的处理:
上面的代码,如果没有添加WM_ERASEBKGND,则默认的消息处理函数DefWindowProc会被调用,此时的DefWindowProc会擦除窗口背景(即绘制背景),并且ps.fErase会为FALSE
如果添加了WM_ERASEBKGND消息,DefWindowProc就不会被调用,则无法擦除窗口背景,并且ps.fErase会为true
2》WM_PAINT的处理
在这个消息中如果调用了hdc = BeginPaint(hWnd, &ps);函数,则此函数只做了两件事:填充ps结构、使窗口重新变得有效
另外DefWindowProc函数也会使窗口变得有效
关于ps.fErase;
这个参数和窗口函数WndProc的返回值有关:
当窗口函数WndProc返回true;则产生WM_PAINT消息时,ps.fErase就为false;表明系统擦除了背景
当窗口函数WndProc返回false;则产生WM_PAINT消息时,ps.fErase就为true;表明系统没有擦除背景
设想一下,当上面的代码中添加了WM_ERASEBKGND消息并在其中直接返回true(这表明系统已经绘制了窗口背景),则ps.fErase就为false
case WM_ERASEBKGND:
return true; //窗口函数WndProc返回true;
注意返回的真或者假只是让程序员可以看见ps.fErase,并作出自己的代码,与窗口的显示即背景没有关系
有些人说当ps.fErase==true,BeginPaint函数会发送一个WM_ERASEBKGND消息,其实BeginPaint并未发出WM_ERASEBKGND消息
4.自己绘制背景或者系统绘制背景。
如果程序员不想系统擦除背景,而自己想绘制背景,怎么办呢?方法是在WM_ERASEBKGND消息处理中添加自己的绘制代码。
对于WM_ERASEBKGND消息,wParam参数保存了用于绘制的设备环境,lParam不使用。
如上面的示例代码,当添加了WM_ERASEBKGND消息,则switch---case中就不会调用DefWindowProc函数绘制背景。这时,程序员自己就可以添加绘制代码
而在基于MFC的程序中,是这样处理自绘代码的:
BOOL CCeDlg::OnEraseBkgnd(CDC* pDC) //这个函数就是WM_ERASEBKGND的消息处理函数
{
// TODO: Add your message handler code here and/or call default
//添加自绘代码
...
return TRUE; //返回真,代表着窗口函数的返回值。以便于程序员在WM_PAINT消息中作出相应处理(如果需要)。这里返回时就不会调用下面的默认处理
//下面将调用系统默认的消息处理函数DefWindowProc进行背景的默认绘制。
return CDialog::OnEraseBkgnd(pDC); //不执行自动生成的这个函数
}
执行这个函数时,提示用户绘制背景,如果用户没有绘制背景,则return CDialog::OnEraseBkgnd(pDC);调用默认的窗口处理函数进行背景的擦除
BOOL CCeDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
//添加自绘背景代码
CBitmap m_bitmap;
BITMAP m_bmInfo;
m_bitmap.LoadBitmap(IDB_BITMAP1);
m_bitmap.GetObject(sizeof(m_bmInfo),&m_bmInfo);
CDC memDC;
memDC.CreateCompatibleDC(pDC);
memDC.SelectObject(&m_bitmap);
GetClientRect(m_rect);
pDC->StretchBlt(0,0,m_rect.Width(),m_rect.Height(),
&memDC,0,0,m_bmInfo.bmWidth,m_bmInfo.bmHeight,SRCCOPY); //内存拷贝函数。绘制背景
memDC.DeleteDC();
return true;//返回真,代表着窗口函数的返回值。以便于程序员在WM_PAINT消息中作出相应处理(如果需要)。这里返回时就不会调用下面的默认处理
//下面将调用系统默认的消息处理函数DefWindowProc进行背景的默认绘制。
return CDialog::OnEraseBkgnd(pDC); //不执行自动生成的这个函数
}
5.WM_ERASEBKGND消息和WM_PAINT消息的另外一种含义:背景色与前景色
可以这样理解WM_ERASEBKGND消息和WM_PAINT消息:
WM_ERASEBKGND消息用于通知系统或者程序员绘制背景色
WM_PAINT消息用于通知程序员绘制前景色,比如在WM_PAINT中调用TextOut函数输出文本
非常感谢原作者的工作,文章地址:http://blog.csdn.net/sdeeds/article/details/6859530
深度分析WM_PAINT和WM_ERASEBKGND消息的更多相关文章
- WM_PAINT和WM_ERASEBKGND消息
1.OnPaint()函数是窗口重绘消息WM_PAINT的响应函数,当窗口重绘时会产生WM_ERASEBKGND消息和WM_PAINT消息,而且WM_ERASEBKGND会先于WM_PAINT产生,所 ...
- WM_PAINT 与 WM_ERASEBKGND消息的深入分析
当WM_PAINT消息不是由函数InvalidateRect产生的时(即通过最大话,最小化,移动,下拉菜单等),系统会先产生连续产生若干个WM_ERASEBKGND消息,紧接着在产生WM_PAINT消 ...
- Invalidate(TRUE)与Invalidate(FALSE)区别(前者会发送WM_ERASEBKGND消息全部刷新,然后使用WM_PAINT消息绘制,而后者只发送WM_PAINT消息)
使用Invalidate(TRUE)函数时,它会向消息队列中添加了WM_ERASEBKGND和WM_PAINT两个消息. 使用Invalidate(FALSE)函数时,它只会向消息队列中添加了WM_P ...
- 窗口绘制有关的消息整理 WM_PAINT, WM_NCPAINT, WM_ERASEBKGND
WM_PAINTWM_PAINT是Windows窗口系统中一条重要的消息,应用程序通过处理该消息实现在窗口上的绘制工作. WM_NCPAINT当窗口客户区以外的部分(如窗口标题栏.菜单栏等)需要需要重 ...
- WM_PAINT与WM_ERASEBKGND(用户操作和API这两种情况产生消息的顺序有所不同)
1)当WM_PAINT不是由InvalidateRect产生时,即由最大化,最小化等产生时,或者移动产生(移动有时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再发 ...
- WM_PAINT产生原因有2种(用户操作和API)——WM_PAINT和WM_ERASEBKGND产生时的先后顺序不一定(四段讨论)
1. 当WM_PAINT不是由InvalidateRect产生时,即由最大化,最小化等产生时,或者移动产生(移动有时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再 ...
- 深度解析VC中的消息(转发)
http://blog.csdn.net/chenlycly/article/details/7586067 这篇转发的文章总结的比较好,但是没有告诉我为什么ON_MESSAGE的返回值必须是LRES ...
- 终于懂了:WM_PAINT 与 WM_ERASEBKGND(三种情况:用户操作,UpdateWindow,InvalidateRect产生的效果并不相同),并且用Delphi代码验证 good
一直对这两个消息的关系不是太了解,借重新深刻学习windows编程的机会研究一番. 1)当窗口从无效变为有效时,比方将部分覆盖的窗口恢复时会重绘窗口时:程序首先会通过发送其他消息调用DefWindow ...
- AndroidService 深度分析(2)
AndroidService 深度分析(2) 上一篇文章我们Service的生命周期进行了測试及总结. 这篇文章我们介绍下绑定执行的Service的实现. 绑定执行的Service可能是仅为本应用提供 ...
随机推荐
- NetSnmp配置
http://blog.csdn.net/shanzhizi/article/details/16985989
- HBase总结(十一)hbase Java API 介绍及使用演示样例
几个相关类与HBase数据模型之间的相应关系 java类 HBase数据模型 HBaseAdmin 数据库(DataBase) HBaseConfiguration HTable 表(Table) H ...
- 全栈JavaScript之路( 二十五 )訪问元素的样式
不论什么支持style 特性的元素在 ,在其DOM 节点 对象中都有一个 style 属性与之相应. 这个style 对象是 CSSStyleDeclaration类型的实例,包括着html sty ...
- 【机器学习实战】第10章 K-Means(K-均值)聚类算法
第 十 章 K-Means(K-均值)聚类算法 K-Means 算法 聚类是一种无监督的学习, 它将相似的对象归到一个簇中, 将不相似对象归到不同簇中.相似这一概念取决于所选择的相似度计算方法.K-M ...
- 小强的HTML5移动开发之路(35)——jQuery中的过滤器详解
1.基本过滤选择器 :first :last :not(selector) :selector匹配的节点之外的节点 :even :偶数 :odd :奇数 :eq(index) :gt(index) : ...
- Hibernate的数据操作(4.*以上版本)
Hibernate的基本数据操作 适用于4.* ..sessionFactory的创建区别 public class NewsTest { private Session session = null ...
- 【hdu2825】ac自动机 + 状压dp
传送门 题目大意: 给你一些密码片段字符串,让你求长度为n,且至少包含k个不同密码片段串的字符串的数量. 题解: 因为密码串不多,可以考虑状态压缩 设dp[i][j][sta]表示长为i的字符串匹配到 ...
- 【30.00%】【vijos 1909】寻找道路
描述 在有向图 G 中,每条边的长度均为 1,现给定起点和终点,请你在图中找一条从起点到 终点的路径,该路径满足以下条件: 路径上的所有点的出边所指向的点都直接或间接与终点连通. 在满足条件 1 的情 ...
- JavaScript函数实现鼠标指向后带图片的提示效果
转载:http://www.cnblogs.com/jack86514/archive/2009/04/01/1427584.html 当我们在写一个网页程序的时候,很多方法可以提供页面的动态显示,从 ...
- 多域名绑定同一IP地址,Node.js来实现
本来打算用Nginx来实现,看了一会Nginx的配置,感觉又要费时间学习,就抱着试试看的心在网上搜,是否可以用Node.js来实现. 没想到,竟然搜到了.想试一下,但国内域名备案时间长达一个月,我肯定 ...