最近和坛子里的同志讨论一个关于重绘的有关问题,大家加入一起探讨下
最近和坛子里的同志讨论一个关于重绘的问题,大家加入一起探讨下
讨论的起因是论坛里的一个帖子里面提到的一个问题:OnPaint 是什么时候调用
然后1#回复的说 OnPaint在初始化界面时候调用一次,之后在手动刷新界面时候调用
然后讨论开始了(下面写a君,但不是1#,是另外一位同志):
a君纠正1#所说的那句话,OnPaint其实在不停的调用,任何时候,除非程序死了,无响应了。当程序没有其它消息时,系统也会发送WM_PAINT给该应用
然后我与a君讨论如果a君的程序不断的收到 WM_PAINT 消息,那他的程序设计就有问题了
因为我自认 WM_PAINT 消息只有在窗口有无效区域的时候窗口才会收到该消息
只要在 WM_PAINT 消息里正确调用了 BeginPaint 与 EndPaint,那么在下次无效区域出现前是不会再收到 WM_PAINT 消息
因为 BeginPaint 会把当前无效区域置为有效
而a君只认为 BeginPaint 只是单纯的填充 PAINTSTRUCT 结构而不会做其它事
下面引用 a君的帖:
我跟你讨论的是你所说的窗“OnPaint在初始化界面时候调用一次,之后在手动刷新界面时候调用”,难道其它时候就不调用了么?比如窗口被其它窗口遮挡等等这些来自于应用程序外部而不是程序内部因素。,
另外 BeginPaint 与 EndPaint只是决定是否要重绘画某些区域,这两个函数本身也是在WM_PAINT响应时调用的,响不响应消息与最终是否进行了绘制是两个概念,
你的错误来源于你对MSDN翻译的错误The system sends an internal WM_PAINT message only once. After an internal WM_PAINT message is returned from GetMessage or PeekMessage or is sent to a window by UpdateWindow, the system does not post or send further WM_PAINT messages until the window is invalidated or until RedrawWindow is called again with the RDW_INTERNALPAINT flag set.。
以上说的是,在GetMessage(或 PeekMessage )返回了一个WM_Paint或者应用本身调用了 UpdateWindow之后,系统只发送WM_PAINT一次。建议你用SPY看看,你的应用没有其它动作时是否会用WM_PAINT消息
系统会在多个不同的时机发送 WM_PAINT 消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个
窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由 系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变 的时候,这一般是通过 一些 函数来完成的。
我的回复:
我还能怎么跟你解释呢,WM_PAINT 其它的发送情况做过自绘窗口的都知道大小改变、遮挡会触发,我不说不代表我不知道
1#所说的 OnPaint在初始化界面时候调用一次,之后在手动刷新界面时候调用 这句话本意也没有错,你只能说他没有说全
但是你所说的 OnPaint其实在不停的调用,任何时候,除非程序死了,无响应了。当程序没有其它消息时,系统也会发送WM_PAINT给该应用,才是错误的说法
我不知道你在写重绘代码的时候,有没有用 SPY++ 去拦截过其它系统的窗口
但我刚开始写自绘窗口时,都是用 SPY++ 来拦截 windows 自己的窗口来跟踪消息
不论拦截的是我自己的窗口,还是 windows 的窗口,根本就不存在不断发送 WM_PAINT 的问题
况且我还没见过哪个程序的窗口会不停的发送 WM_PAINT 消息的
如果你用 SPY++ 拦截你自己的程序,发现很多连续的 WM_PAINT 消息的话,我才会 7# 说出你写的程序有问题
BeginPaint 并不是决定那些区域需要重绘,需要重绘的区域在 WM_PAINT 消息发出前系统就已经计算好了的
调用 BeginPaint 的目的在于告诉系统我已经把这个区域绘画好了,你不用再给我发送 WM_PAINT 消息了
也就是说 WM_PAINT 只有在有需要重绘的区域时才会发送
那么为什么 Invalidate、RedrawWindow、UpdateWindow、窗口大小改变、被遮挡后恢复等等这些为什么会触发 WM_PAINT ?主要原因不就是因为这些函数或者动作,会使得窗口的部分或全部区域无效,需要用户或系统重新绘制,所以才会给窗口发送 WM_PAINT 消息
a君的帖:
是Windows系统给任何程序发送WM_PAINT,程序被动响应这个消息,不是应用程序发送这个消息,这个不停的是相对于你们所说的"程序初始化时发送一次,之后在手动刷新界面时候调用"之类的话的。应用程序在空闲时,消息队列中没有其它消息时,系统也会向其发送WM_PAINT,每当消息循环空的时候,如果Windows发现存在一个无效区域,就会放入一个WM_PAINT消息,以上情况不包括应用程序主动强制自已绘制窗口的情形
因为屏幕是在不断的刷新的,问你一下,你的应用没有其它动作时是否会收到WM_PAINT消息,如果没有,窗体是如何保持在屏幕上可见的?当然了,窗体可见,还要响应其它消息,比如WM_NCXXXX,WM_ERASEBKND等,WM_PAINT只是其中这一
调用 BeginPaint 的目的在于告诉系统我已经把这个区域绘画好了,你不用再给我发送 WM_PAINT 消息了
以上这句话你是从哪儿看到的,毫无根据。
The BeginPaint function prepares the specified window for painting and fills a PAINTSTRUCT structure with information about the painting.
BeginPaint其中之一的的作用是填充结构体PAINTSTRUCT,而这个结构体中包含了需要绘制的区域。英文看不懂,不知你哪来的这么多自信
我的回复:
你自己也说了,只有在发现有无效区域后系统才会发 WM_PATIN
至于你说的 以上情况不包括应用程序主动强制自已绘制窗口的情形 你又说错了
那如果我在程序里调用 Invalidate 呢?这个属于什么情况
Invalidate 本来就是强制让窗口中的某个或整个区域无效
那既然无效就当然会发送 WM_PAINT 消息了
相对来说,如果你在响应 WM_PATIN 消息的时候,正确的处理了无效区域,那么系统怎么还会继续给你发送 WM_PAINT
看来你是把显示器的刷新率和系统的刷新搞混了
你自己看看 BeginPaint 的 Remark 部分,其它我就不多说了
对于一样事物一知半解是很可怕的,又不接受别人的纠正就更可怕了
到此我已经开始凌乱了,大家一起加入讨论一下吧
------解决思路----------------------
这有啥好难理解的,Windows自己有一个位图是分辨率那么大的。 当显示器需要刷新的时候是从这个位图拷贝到显示器上。
窗口的默认DC其实就是画到这个位图上的(CPaintDC, CClientDC)。
窗口会在这个位图上占一块位置,如果位图上有窗口的要显示的可见内容,是不会发送WM_PAINT的,比如在前台时的窗口移动。
当位图上缺失要显示的内容时就会发送WM_PAINT,比如窗口创建的时候,调整大小的时候等等。
Invalidate 就是告诉系统窗口的那块矩形的显示内容已经过期了,强制造成位图上内容的缺失。
------解决思路----------------------
路过,没有深入了解。
要我就直接申明个变量进行累加,然后弄个文本框显示变量。
------解决思路----------------------
afx_msg void OnPaint( );
说明:
当Windows或应用程序请求重画应用程序窗口的一部分时,框架调用这个成员函数。WM_PAINT在调用UpdateWindow或RedrawWindow成员函数时发出。当设置了RDW_INTERNALPAINT标志并调用RedrawWindow成员函数时,窗口可能会接收到内部重画消息。在这种情况下,窗口可能没有更新区域。应用程序必须调用GetUpdateRect成员函数以确定窗口是否具有更新区域。如果GetUpdateRect返回0,则应用程序不应调用BeginPaint和EndPaint成员函数。
应用程序负责检查是否需要内部重画或更新,这可通过查看每条WM_PAINT消息的内部数据结构来完成,因为一条WM_PAINT可能是由于一个无效区域或由于使用RDW_INTERNALPAINT标志调用了RedrawWindow成员函数而引起的。
Windows只发送一次内部WM_PAINT消息。在通过UpdateWindow成员函数向窗口发送了内部WM_PAINT消息以后,将不会再向窗口发送其它WM_PAINT消息,直到再次使用RDW_INTERNALPAINT标志调用了RedrawWindow成员函数。
------解决思路----------------------
如果没记错的话,我就是那个#1!!!我当时的理解是,只有界面需要重绘的时候才调用WM_PAINT ,因为如果一直重绘的话。。那界面会“呼呼”的闪!
------解决思路----------------------
明显不可能不停的调用,违背常理
------解决思路----------------------
找了好久没找到那个帖子。。让他自己慢慢理解吧,只要他一直在做VC,迟早会明白^_^
------解决思路----------------------
这种不见结果的讨论谁也说服不了谁,用我的驻留软件在OnPaint( )中放一个弹出信息,编译后运行程序,在接收窗口就能看到你的什么动作会引发消息,眼见为实。
------解决思路----------------------
调试时断点打在OnPaint()函数下不断按F5会出现这一现象
------解决思路----------------------
居然你俩还在这里讨论这个问题,其实只要应用程序需要重绘时,系统就会发送WM_PAINT消息了,这个重绘可能是窗口重叠,鼠标移动等相关操作导致的。也没必要钻牛角尖了。
讨论的起因是论坛里的一个帖子里面提到的一个问题:OnPaint 是什么时候调用
然后1#回复的说 OnPaint在初始化界面时候调用一次,之后在手动刷新界面时候调用
然后讨论开始了(下面写a君,但不是1#,是另外一位同志):
a君纠正1#所说的那句话,OnPaint其实在不停的调用,任何时候,除非程序死了,无响应了。当程序没有其它消息时,系统也会发送WM_PAINT给该应用
然后我与a君讨论如果a君的程序不断的收到 WM_PAINT 消息,那他的程序设计就有问题了
因为我自认 WM_PAINT 消息只有在窗口有无效区域的时候窗口才会收到该消息
只要在 WM_PAINT 消息里正确调用了 BeginPaint 与 EndPaint,那么在下次无效区域出现前是不会再收到 WM_PAINT 消息
因为 BeginPaint 会把当前无效区域置为有效
而a君只认为 BeginPaint 只是单纯的填充 PAINTSTRUCT 结构而不会做其它事
下面引用 a君的帖:
我跟你讨论的是你所说的窗“OnPaint在初始化界面时候调用一次,之后在手动刷新界面时候调用”,难道其它时候就不调用了么?比如窗口被其它窗口遮挡等等这些来自于应用程序外部而不是程序内部因素。,
另外 BeginPaint 与 EndPaint只是决定是否要重绘画某些区域,这两个函数本身也是在WM_PAINT响应时调用的,响不响应消息与最终是否进行了绘制是两个概念,
你的错误来源于你对MSDN翻译的错误The system sends an internal WM_PAINT message only once. After an internal WM_PAINT message is returned from GetMessage or PeekMessage or is sent to a window by UpdateWindow, the system does not post or send further WM_PAINT messages until the window is invalidated or until RedrawWindow is called again with the RDW_INTERNALPAINT flag set.。
以上说的是,在GetMessage(或 PeekMessage )返回了一个WM_Paint或者应用本身调用了 UpdateWindow之后,系统只发送WM_PAINT一次。建议你用SPY看看,你的应用没有其它动作时是否会用WM_PAINT消息
系统会在多个不同的时机发送 WM_PAINT 消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个
窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由 系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变 的时候,这一般是通过 一些 函数来完成的。
我的回复:
我还能怎么跟你解释呢,WM_PAINT 其它的发送情况做过自绘窗口的都知道大小改变、遮挡会触发,我不说不代表我不知道
1#所说的 OnPaint在初始化界面时候调用一次,之后在手动刷新界面时候调用 这句话本意也没有错,你只能说他没有说全
但是你所说的 OnPaint其实在不停的调用,任何时候,除非程序死了,无响应了。当程序没有其它消息时,系统也会发送WM_PAINT给该应用,才是错误的说法
我不知道你在写重绘代码的时候,有没有用 SPY++ 去拦截过其它系统的窗口
但我刚开始写自绘窗口时,都是用 SPY++ 来拦截 windows 自己的窗口来跟踪消息
不论拦截的是我自己的窗口,还是 windows 的窗口,根本就不存在不断发送 WM_PAINT 的问题
况且我还没见过哪个程序的窗口会不停的发送 WM_PAINT 消息的
如果你用 SPY++ 拦截你自己的程序,发现很多连续的 WM_PAINT 消息的话,我才会 7# 说出你写的程序有问题
BeginPaint 并不是决定那些区域需要重绘,需要重绘的区域在 WM_PAINT 消息发出前系统就已经计算好了的
调用 BeginPaint 的目的在于告诉系统我已经把这个区域绘画好了,你不用再给我发送 WM_PAINT 消息了
也就是说 WM_PAINT 只有在有需要重绘的区域时才会发送
那么为什么 Invalidate、RedrawWindow、UpdateWindow、窗口大小改变、被遮挡后恢复等等这些为什么会触发 WM_PAINT ?主要原因不就是因为这些函数或者动作,会使得窗口的部分或全部区域无效,需要用户或系统重新绘制,所以才会给窗口发送 WM_PAINT 消息
a君的帖:
是Windows系统给任何程序发送WM_PAINT,程序被动响应这个消息,不是应用程序发送这个消息,这个不停的是相对于你们所说的"程序初始化时发送一次,之后在手动刷新界面时候调用"之类的话的。应用程序在空闲时,消息队列中没有其它消息时,系统也会向其发送WM_PAINT,每当消息循环空的时候,如果Windows发现存在一个无效区域,就会放入一个WM_PAINT消息,以上情况不包括应用程序主动强制自已绘制窗口的情形
因为屏幕是在不断的刷新的,问你一下,你的应用没有其它动作时是否会收到WM_PAINT消息,如果没有,窗体是如何保持在屏幕上可见的?当然了,窗体可见,还要响应其它消息,比如WM_NCXXXX,WM_ERASEBKND等,WM_PAINT只是其中这一
调用 BeginPaint 的目的在于告诉系统我已经把这个区域绘画好了,你不用再给我发送 WM_PAINT 消息了
以上这句话你是从哪儿看到的,毫无根据。
The BeginPaint function prepares the specified window for painting and fills a PAINTSTRUCT structure with information about the painting.
BeginPaint其中之一的的作用是填充结构体PAINTSTRUCT,而这个结构体中包含了需要绘制的区域。英文看不懂,不知你哪来的这么多自信
我的回复:
你自己也说了,只有在发现有无效区域后系统才会发 WM_PATIN
至于你说的 以上情况不包括应用程序主动强制自已绘制窗口的情形 你又说错了
那如果我在程序里调用 Invalidate 呢?这个属于什么情况
Invalidate 本来就是强制让窗口中的某个或整个区域无效
那既然无效就当然会发送 WM_PAINT 消息了
相对来说,如果你在响应 WM_PATIN 消息的时候,正确的处理了无效区域,那么系统怎么还会继续给你发送 WM_PAINT
看来你是把显示器的刷新率和系统的刷新搞混了
你自己看看 BeginPaint 的 Remark 部分,其它我就不多说了
对于一样事物一知半解是很可怕的,又不接受别人的纠正就更可怕了
到此我已经开始凌乱了,大家一起加入讨论一下吧
------解决思路----------------------
这有啥好难理解的,Windows自己有一个位图是分辨率那么大的。 当显示器需要刷新的时候是从这个位图拷贝到显示器上。
窗口的默认DC其实就是画到这个位图上的(CPaintDC, CClientDC)。
窗口会在这个位图上占一块位置,如果位图上有窗口的要显示的可见内容,是不会发送WM_PAINT的,比如在前台时的窗口移动。
当位图上缺失要显示的内容时就会发送WM_PAINT,比如窗口创建的时候,调整大小的时候等等。
Invalidate 就是告诉系统窗口的那块矩形的显示内容已经过期了,强制造成位图上内容的缺失。
------解决思路----------------------
路过,没有深入了解。
要我就直接申明个变量进行累加,然后弄个文本框显示变量。
------解决思路----------------------
afx_msg void OnPaint( );
说明:
当Windows或应用程序请求重画应用程序窗口的一部分时,框架调用这个成员函数。WM_PAINT在调用UpdateWindow或RedrawWindow成员函数时发出。当设置了RDW_INTERNALPAINT标志并调用RedrawWindow成员函数时,窗口可能会接收到内部重画消息。在这种情况下,窗口可能没有更新区域。应用程序必须调用GetUpdateRect成员函数以确定窗口是否具有更新区域。如果GetUpdateRect返回0,则应用程序不应调用BeginPaint和EndPaint成员函数。
应用程序负责检查是否需要内部重画或更新,这可通过查看每条WM_PAINT消息的内部数据结构来完成,因为一条WM_PAINT可能是由于一个无效区域或由于使用RDW_INTERNALPAINT标志调用了RedrawWindow成员函数而引起的。
Windows只发送一次内部WM_PAINT消息。在通过UpdateWindow成员函数向窗口发送了内部WM_PAINT消息以后,将不会再向窗口发送其它WM_PAINT消息,直到再次使用RDW_INTERNALPAINT标志调用了RedrawWindow成员函数。
------解决思路----------------------
如果没记错的话,我就是那个#1!!!我当时的理解是,只有界面需要重绘的时候才调用WM_PAINT ,因为如果一直重绘的话。。那界面会“呼呼”的闪!
------解决思路----------------------
明显不可能不停的调用,违背常理
------解决思路----------------------
找了好久没找到那个帖子。。让他自己慢慢理解吧,只要他一直在做VC,迟早会明白^_^
------解决思路----------------------
这种不见结果的讨论谁也说服不了谁,用我的驻留软件在OnPaint( )中放一个弹出信息,编译后运行程序,在接收窗口就能看到你的什么动作会引发消息,眼见为实。
------解决思路----------------------
这种不见结果的讨论谁也说服不了谁,用我的驻留软件在OnPaint( )中放一个弹出信息,编译后运行程序,在接收窗口就能看到你的什么动作会引发消息,眼见为实。
用不着这么复杂吧,随便 TRACE 或者 OutputDebugString 在输出栏打印消息不就行了
其实我跟对方讨论的重点在于为什么他的程序会连续收到 wm_paint 消息,而他认为是正常的
那么你认为如果你的程序经常收到 wm_paint 消息的话属于正常吗?
调试时断点打在OnPaint()函数下不断按F5会出现这一现象
------解决思路----------------------
居然你俩还在这里讨论这个问题,其实只要应用程序需要重绘时,系统就会发送WM_PAINT消息了,这个重绘可能是窗口重叠,鼠标移动等相关操作导致的。也没必要钻牛角尖了。