无效区域 更新区域 重画 WM_PAINT 的关系和机制

1. 系统何时发送WM_PAINT消息?

  系统会在多个不同的时机发送 WM_PAINT 消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个

窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由系统管理的,应用只是被动地接收该消息,在消息处理

函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一

般是通过 InvalidateRect 和 InvalidateRgn 函数来完成的。

  InvalidateRect 和 InvalidateRgn把指定的区域加到窗口的 Update Region 中,当应用的消息队列没有其他

消息时,如果窗口的 Update Region 不为空时,系统就会自动产生 WM_PAINT 消息。

  系统为什么不在调用 Invalidate 时发送 WM_PAINT 消息呢?又为什么非要等应用消息队列为空时才发送

WM_PAINT 消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽可能地推后做。

  不过这样也有利于提高绘制的效率:两个 WM_PAINT 消息之间通过 InvalidateRect 和InvaliateRgn 使之失效

的区域就会被累加起来,然后在一个 WM_PAINT 消息中一次得到 更新,不仅能避免多次重复地更新同一区域,也优化了

应用的更新操作。

  这种通过 InvalidateRect 和 InvalidateRgn 来使窗口区域无效,依赖于系统在合适的时机发送 WM_PAINT

消息的机 制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送 WM_PAINT 消息之间是有延迟的;有时候

这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用 SendMessage 发送一条 WM_PAINT消息来强

制立即重画,但不如使用 Windows GDI 为我们提供的更方便和强大的函数: UpdateWindow 和 RedrawWindow。

  UpdateWindow 会检查窗口的 Update Region,当其不为空时才发送 WM_PAINT 消息; RedrawWindow 则给我

们更多的控制:是否重画非客户区和背景,是否总是发送 WM_PAINT 消息而不管 Update Region 是否为空等。

2. BeginPaint

  BeginPaint 和 WM_PAINT 消息紧密相关。试一试在 WM_PAINT 处理函数中不写 BeginPaint 会怎样?程序会像

进入了一个死循环一样达到惊人的CPU占用率,你会发现程序总在处理一个接 一个的 WM_PAINT 消息。这是因为在通常情

况下,当应用收到 WM_PAINT 消息时,窗口的 Update Region 都是非空的(如果为空就不需要发送WM_PAINT 消息

了), BeginPaint 的一个作用就是把该 Update Region 置为空,这样如果不调用 BeginPaint,窗口的

Update Region 就一直不为空,如前所述,系统就会一直发送 WM_PAINT 消息。

  BeginPaint 和 WM_ERASEBKGND 消息也有关系。当窗口的 Update Region 被标志为需要擦除背景时,

BeginPaint 会发送 WM_ERASEBKGND 消息来重画背景,同时在其返回信息里有一个标志表明窗口背景是否被重画过。

  当我们用 InvalidateRect 和 InvalidateRgn 来把指定区域加到 Update Region 中时,可以设置该区域是否

需要被擦除背景,这样下一个 BeginPaint 就知道是否需要发送 WM_ERASEBKGND 消息了。

  另外要注意的一点是,BeginPaint 只能在 WM_PAINT 处理函数中使用。

补充几点:

1.WM_Paint 是一个被动消息,不能通过普通的方法简单的 sendmessage WM_paint 了事

这是不行的;但通过消息由程序员引发不是不可能;通过几个特殊的常数可以做到,不过要到delphi下找

2.sendmessage 可以将消息发送到消息队列;但windows会自动判断是否存在无效的画图区域;

如果存在无效的画图区域,则可能会重画,反之则弃用该消息.

3.可以使用 InvalidateRect 等几个APi将屏幕上任意一个个矩形区域设置为无效区域,在UpdateWindow后调用后,windows会自动查找是否存在无效,并重画,该矩形区域; 

-------------------------------------------------------------------------------------------

我们知道,绘制窗口的内容,通常是在 WM_PAINT 消息的处理中。这个消息会由各种各样的情况触发,例如,某个覆盖在自己之上的窗口关闭了,等等。不过,出于效率考虑,WM_PAINT 消息的产生并不意味着整个窗口都需要重新绘制,考虑刚才说的例子,原来有个窗口 B 覆盖了窗口 A 的左上角,则窗口 B 关闭后,窗口 A 就会收到一条 WM_PAINT 消息,但是需要绘制的其实只是左上角的那一部分(为了讨论简单,不考虑窗口自身的内容一直在变化的情况,如动画窗口之类的)。这里的左上角,正是一个“无效区域”。由此,我们可以解释一下 InvalidateRect 函数了,这个函数的作用非常简单,就是把某个矩形加入到窗口的无效区域列表中,告诉窗口在处理下一条 WM_PAINT 消息时,这个区域是需要重新绘制的。顺便说一下,InvalidateRect 有个兄弟函数,不止是简单地处理一个矩形,而是处理一个区域,那就是 InvalidateRgn 函数,道理是一样的。

我们还知道,在大多数情况下,WM_PAINT 消息并不是我们程序自己发出的,而是窗口管理器(也可看做就是 Windows 系统本身)发出的。不过,系统还是给了我们自己发送这个消息的途径,那就是 UpdateWindow 函数。我们常常会看到,经典的 SDK 风格的 Hello World 程序里,主窗口创建之后,会调用 ShowWindow 函数把它显示出来,跟跟着立刻有一个 UpdateWindow 的调用,就是为了主窗口显示之后能立即进行第一次刷新。不过这是在 32 位 Windows 之前的情况,现在窗口管理器已经比原来更聪明,所以 UpdateWindow 函数的作用已经相当弱化了,不再被频繁使用。当然,UpdateWindow 还会做一些自己的判断,仅当无效区域存在时才会真正发出 WM_PIANT 消息。

至于 RedrawWindow 函数,其实现非常复杂,不过我们可以不去理会无关宏旨的部分。大概说来,如果你了解 PostMessage 和 SendMessage 的区别,那就会比较好理解。一般的 WM_PAINT 消息都是被投递到了消息队列里,等待 GetMessage 将之取出后进行处理。而 RedrawWindow 会强行直接调用窗口的回调函数就地处理。说白了,就是异步和同步的区别。这个函数允许指定要重绘的部分(矩形或者区域),因此,也常被人说这个函数兼具前面两个函数的功效。

-------------------------------------------------------------------------------------------

下面代码是按钮画圆功能,在画之前需要清除上一次的画的圆

void CCircleDlg::OnButton1()  

{

// TODO: Add your control notification handler code here

//RedrawWindow();

//InvalidateRect(NULL);

//UpdateWindow();

UpdateData(TRUE);

CDC *pDC=GetDC();

pDC->SetMapMode( MM_LOMETRIC );

int Horzsize=pDC->GetDeviceCaps(HORZSIZE);

int Vertsize=pDC->GetDeviceCaps(VERTSIZE);

if(m_Width!=0 && m_Height!=0)

pDC->Ellipse(0,0,10*m_Radius*Horzsize/m_Width,0-10*m_Radius*Vertsize/m_Height);//注意由于是UINT类型注意第4个参数的写法

}

先说明下WM_PAINT是低级别的消息,Windows总是在消息循环空的时候才把WM_PAINT放入其中,实际上,Windows为每个窗口维护一个“绘图信息结构”,无效区域的坐标就在其中,每当消息循环空的时候,如果Windows发现存在一个无效区域,就会放入一个WM_PAINT消息。

1.WM_PAINT加入消息队列等待处理 - InvalidateRect , InvalidateRect 有个兄弟函数,不止是简单地处理一个矩形,而是处理一个区域,那就是 InvalidateRgn 函数

2.如果有无效区域,直接发送WM_PAINT到消息处理函数进行处理,不经过消息循环队列;如果没有无效区域啥事也不干 - UpdateWindow  

3.RedrawWindow可以[指定无效区域(包括矩形和路径)]这部分相当于InvalidateRect和InvalidateRgn,[并直接发送WM_PAINT到消息处理函数进行处理]这部分相当于UpdateWindow,相当于以上两者的综合。

所以上例有两种处理方法:

1.直接加个RedrawWindow了事,这时认为所有区域都是无效,直接发送WM_PAINT到消息处理函数进行处理。

2.加两句

InvalidateRect(NULL);

UpdateWindow();

第一句使整个区域无效

第二句UpdateWindow检测到有无效区域,直接发送WM_PAINT到消息处理函数进行处理。
已标记关键词 清除标记
相关推荐
简介 笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、中级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!此时此景,笔者只专注Android、Iphone等移动平台开发,看着这些源码心中有万分感慨,写此文章纪念那时那景! Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机的流程及操作:获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除,从账户中取出amt,如果amt>账户余额抛出异常,一个实体Bean可以表示不同的数据实例,我们应该通过主键来判断删除哪个数据实例…… ejbCreate函数用于初始化一个EJB实例 5个目标文件,演示Address EJB的实现 ,创建一个EJB测试客户端,得到名字上下文,查询jndi名,通过强制转型得到Home接口,getInitialContext()函数返回一个经过初始化的上下文,用client的getHome()函数调用Home接口函数得到远程接口的引用,用远程接口的引用访问EJB。 EJB中JNDI的使用源码例子 1个目标文件,JNDI的使用例子,有源代码,可以下载参考,JNDI的使用,初始化Context,它是连接JNDI树的起始点,查找你要的对象,打印找到的对象,关闭Context…… ftp文件传输 2个目标文件,FTP的目标是:(1)提高文件的共享性(计算机程序和/或数据),(2)鼓励间接地(通过程序)使用远程计算机,(3)保护用户因主机之间的文件存储系统导致的变化,(4)为了可靠和高效地传输,虽然用户可以在终端上直接地使用它,但是它的主要作用是供程序使用的。本规范尝试满足大型主机、微型主机、个人工作站、和TACs 的不同需求。例如,容易实现协议的设计。 Java EJB中有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天通信演示代码 2个目标文件,一个服务器,一个客户端。 Java Telnet客户端实例源码 一个目标文件,演示Socket的使用。 Java 组播组中发送和接受数据实例 3个目标文件。 Java读写文本文件的示例代码 1个目标文件。 java俄罗斯方块 一个目标文件。 Java非对称加密源码实例 1个目标文件 摘要:Java源码,算法相关,非对称加密   Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。   设定字符串为“张三,你好,我是李四”   产生张三的密钥对(keyPairZhang)   张三生成公钥(publicKeyZhang)并发送给李四,这里发送的是公钥的数组字节   通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到张三编码后的公钥,将其解码,李四用张三的公钥加密信息,并发送给李四,张三用自己的私钥解密从李四处收到的信息…… Java利用DES私钥对称加密代码实例 同上 java聊天室 2个目标文件,简单。 java模拟掷骰子2个 1个目标文件,输出演示。 java凭图游戏 一个目标文件,简单。 java求一个整数的因子 如题。 Java生成密钥的实例 1个目标文件 摘要:Java源码,算法相关,密钥   Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从文件中得到公钥编码的字节数组、如何从字节数组解码公钥。 Java数据压缩与传输实例 1个目标文件 摘要:Java源码,文件操作,数据压缩,文件传输   Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页