今天遇到一个比较有意思的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. 一个sendMessage

    Message 1.判断是否同意协议.2.验证验证码是否正确.3.验证手机是否符合规则,符合规则就用message()发送短信,验证码的有效期以及使用的短信模板,在配置文件中进行管理.返回值下标为st ...

  2. 在windows 下安装启动redis

    在windows环境下安装 redis这个需要在github中下载开源代码,https://github.com/mythz/redis-windows 下载最近的zip包然后 解压到任意一个盘符中进 ...

  3. ie8 iframe去掉边框的属性

    <iframe src="" id="Iframe" height="200" frameborder="0" s ...

  4. Hadoop2.6.0子项目hadoop-mapreduce-examples的简单介绍

    引文 学习Hadoop的同学们,一定知道如果运行Hadoop自带的各种例子,以大名鼎鼎的wordcount为例,你会输入以下命令: hadoop org.apache.hadoop.examples. ...

  5. Trie树(c++实现)

    转:http://www.cnblogs.com/kaituorensheng/p/3602155.html http://blog.csdn.net/insistgogo/article/detai ...

  6. Samba的安装与配置

    Samba的安装与配置: 准备:关闭其他虚拟设备 #/etc/init.d/libvirtd stop #/etc/init.d/xend stop #chkconfig libvirtd off # ...

  7. easyui datagrid中关联combox

    datagrid中列上关联combobox{ field: 'SysCode', title: '系统代码', width: 150, align: 'left', editor: { type: ' ...

  8. C# 实现 任意多边形切割折线算法

    1.    内容简介 本文旨在解决任意多边形切割折线,获取切割之后的折线集合. 本文实现的算法内容包括:判断两条线段是否相交,如若相交,获取交点集合.对线上的点集,按斜率方向排序.判断点是否在多边形内 ...

  9. [spark案例学习] 单词计数

    数据准备 数据下载:<莎士比亚全集> 我们先来看看原始数据:首先将数据加载到RDD,然后显示数据框的前15行. shakespeareDF = sqlContext.read.text(f ...

  10. 使用TableViewRow完成下拉菜单效果

    在TableViewRow标签中增加一个属性opened(自己定义的)=true or false 用于标记当前row的状态 ​    ​然后用table_view.deleteRow()方法  参数 ...