今天遇到一个比较有意思的bug, 这里简单记录下。

Bug的症状是通过拖拉边框把我们客户端主窗口拖小之后,再最大化,会发现窗口显示有问题, 看起来像是刷新问题, 有些地方显示的不对了。
这里要说明的是我这里的主窗口是非常复杂的窗口, 里面集成了很多组件(cpmponent),有很多层的子窗口。 这个问题只有在特定条件下才会发生, 正常情况下都是好的。
遇到这种问题,我们怎么处理? 
首先当然是观察症状, 究竟是刷新问题, 还是Layout出错了。
我们可以通过Spy++查看窗口层次是不是正确, 窗口位置是不是对的。
查看结果是窗口的层次和Layout位置都没有问题。
既然我们这里遇到的刷新问题,所以我们要从WM_PAINT消息着手, 我们通过Spy++查看相关窗口的WM_PAINT是不是正确。
很快我们就会发现某个窗口正在不停地收到WM_PAINT消息, 很可能与我们的bug有关。
一个窗口不停的收到WM_PAINT重画, 无非大概有几类原因:
正常情况是我们正在做动画, 可能是通过定时器之类的东西让窗口不停地InvalidateRect重画某块区域, 我们的窗口明显不属于这种情况。
讨论异常情况前先讨论WM_PAINT消息,我们知道WM_PAINT消息里一定要调用BeginPaint和EndPaint, 前者告诉系统绘画开始,系统会把当前窗口的无效区域变得有效, 后者结束某次绘画。
异常情况有时是WM_PAINT消息里我们的消息处理函数在某些条件下直接返回了,从而没有调用BeginPaint告诉窗口无效区域已经有效, 这样会因为因为窗口一直有无效区域存在,导致窗口一直收到WM_PAINT消息。
还有一种异常情况情况是我们是在WM_PAINT消息里调用BeginPaint后又调用了InvalidateRect, 这样会导致窗口后面会再次收到WM_PAINT消息, 最后窗口陷入WM_PAINT的死循环。 
那么我们这里的问题窗口属于哪类? 用什么方法可以判断出来?
注意到这里关键的三个API:BeginPaint, EndPaint, InvalidateRect的第一个参数都是窗口句柄, 我们可以通过WinDbg的API断点来跟踪执行过程, Attach WinDbg到我们的主窗口进程,比如我们的窗口句柄是0x209A0, 我们可以这样设置API断点:
bp USER32!NtUserInvalidateRect ".if(dwo(@esp+0x4)==0x209A0) {kv;gc} .else {gc}"
bp USER32!NtUserBeginPaint ".if(dwo(@esp+0x4)==0x209A0) {kv;gc} .else {gc}"
bp USER32!NtUserEndPaint ".if(dwo(@esp+0x4)==0x209A0) {kv;gc} .else {gc}"
上面的条件断点表示,当调用我们的对应的API,并且第一个参数(窗口句柄)是我们的目标窗口时,打印堆栈。
很快我定位出Bug发生的原因了, 条件断点显示了API如下的调用次序:
BeginPaint->InvalidateRect->InvalidateRect->EndPaint
找到Bug的原因后,然后把Bug assign给该模块的负责人。 (看我够意思吧,不仅找到原因,还把调用栈都提供了)
另外 ,后面还发现这个bug发生时窗口的某些行为会不对, 测试发现原因是所有窗口的定时器都不能正常工作了。
关于这个问题, 你能想到原因吗? 
如果想不到, 请把我的这篇博客《从点击Button到弹出一个MessageBox, 背后发生了什么》看一遍。
如果看了还想不到, 重点看第4条。
最后, 简单总结下:计算机的好处是它永远不会欺骗你, 它只会按部就班的执行, 所以很多看似奇怪(甚至看似不可思议的问题), 只要你理解了程序背后的机制原理,都是可以找出根本原因的。

记一个界面刷新相关的Bug的更多相关文章

  1. VUE的一个数据绑定与页面刷新相关的bug

    1.场景: N层嵌套的循环查询业务场景,框架是vue.其中在最后一层查完之后,还需要查其中每一项的两个属性,类型都是列表.查完之后将其赋值给一个变量用于页面展示.代码如下: (1)异常代码: getS ...

  2. MFC截图和界面刷新相关问题

    问题描写叙述:         就是首先用CDC来截图,保存图片的路径通过dlg窗体来手动设置并传入.但是截下来的图片就会连带那个对话框也截图下来.         就是这样.我想截后面那个图.前面这 ...

  3. iOS开发——常见错误——使用MJRefresh返回上一个界面蹦掉的情况

    最近在使用MJRefresh框架时发现了一个bug 下面是我的源代码 前一个界面 -(void)tableView:(UITableView *)tableView didSelectRowAtInd ...

  4. Android界面刷新之invalidate与postInvalidate的区别

    Android的invalidate与postInvalidate都是用来刷新界面的. 在UI主线程中,用invalidate():本质是调用View的onDraw()绘制. 主线程之外,用postI ...

  5. 记一个社交APP的开发过程——基础架构选型(转自一位大哥)

    记一个社交APP的开发过程——基础架构选型 目录[-] 基本产品形态 技术选型 最近两周在忙于开发一个社交App,因为之前做过一点儿社交方面的东西,就被拉去做API后端了,一个人头一次完整的去搭这么一 ...

  6. Android之界面刷新(invalidate和postInvalidate使用)

    Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用. Android提供了Inva ...

  7. Android界面刷新

    Android的invalidate与postInvalidate都是用来刷新界面的,用法区别在于: 1)invalidate():实例化一个Handler对象,并重写handleMessage方法调 ...

  8. Android界面刷新方法

    Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中 ...

  9. IE的CSS相关的BUG(整理一)

    本来不想弄这个ie的bug的,真的很想让它快点死掉,可是事与愿违啊,没办法,还是贴出来,以备自用. 这个网页(http://haslayout.net/css/index)上例举了所有的IE和CSS相 ...

随机推荐

  1. 一起买beta版PHP单元测试

    一起买beta版PHP单元测试 测试目的 保证代码质量,对各个单元进行测试,可以有效地保证代码的可靠性,让模块在与别的模块整合时出现更少的错误. 单元描述 完成帖子接口 ​ 将"正在进行&q ...

  2. c# json转换实例

    用户实体: public class UserModel { public long? UserId { get; set; } public string UserName { get; set; ...

  3. 禁止VMware虚拟机与Host的时间同步

    禁止VMware虚拟机与Host的时间同步 1. 查看虚拟机是否安装了 VMware Tools, 如果有安装,则将 VMware Tools 属性窗口的“选项”-->“其他选项”中“虚拟机与宿 ...

  4. Mac 下安装ruby,以及CocoaPods安装以及使用网摘

    ruby安装网址:https://ruby-china.org/wiki/install_ruby_guide CocoaPods安装和使用教程地址:http://code4app.com/artic ...

  5. Objective-C Runtime

    原文地址:http://tech.glowing.com/cn/objective-c-runtime/ 原作者:顾鹏 如有侵权,请联系本人删除 Objective-C Objective-C 扩展了 ...

  6. Android中Handler 、Thread和Runnable之间的关系

    在多线程编程的时候,我们经常会用到Handler,Thread和Runnable这三个类,我们来看看这三个类之间是怎么样的关系? 首先说明Android的CPU分配的最小单元是线程,Handler一般 ...

  7. Linux内核分析之理解进程调度时机跟踪分析进程调度与进程切换的过程

    一.原理分析 1.调度时机 背景不同类型的进程有不同的调度需求第一种分类I/O-bond:频繁的进行I/O:通常会花费很多时间等待I/O操作的完成CPU-bound:计算密集型:需要大量的CPU时间进 ...

  8. Tomcat settings should be set in Tomcat Preference Page解决

    选择tomcat version,然户找到该tomcat的主目录,输入在tomcat home,下面的参数会自动配置,点击apply. 然后在左边选择advanced,同样把tomcat的主目录复制在 ...

  9. mongodb安装及基础命令

    安装mongodb(mongodb-linux-x86_64-3.2.4.tgz)1 export PATH=$PATH:/usr/local/mongodb/bin2 /usr/local/mong ...

  10. java-API中的常用类,新特性之-泛型,高级For循环,可变参数

    API中的常用类 System类System类包含一些有用的类字段和方法.它不能被实例化.属性和方法都是静态的. out,标准输出,默认打印在控制台上.通过和PrintStream打印流中的方法组合构 ...