再谈向RichEdit中插入GIF动画的实现

我的前一篇文章“使用定时器显示GIF动画的ATL控件实现”中讲述了如何创建ATL项目,并实现显示GIF动画的控件,虽然能够显示,但有一些问题:

1. tphlj同学说一行插入多个GIF的时候,CPU会很高。

    这个问题我倒没有注意,因为我发现了更严重的问题,所以用那种方法实现的控件没有使用了,已经被我删掉,不能测试。

2. 删除插入的GIF后发现光标还在不停的闪烁

    显然这是由于删除对象后对象没有被释放,定时器还处于启动状态,导致不断地刷新;原因找了很久,发现是由于RichEdit支持撤消造成的,对象虽然删除了,但为了支持撤消,IRichEditOle中还保留有对象的引用,所以没有被释放,定时器也一直处于启动状态。当然,这可以通过删除对象时停止计时器完成,但是这样会发现撤消后动画不会动了;当然这也好解决,因为只要对象变得可见时,OnDraw函数会被触发,所以可以用一个BOOL值来存储定时器是否处于启动状态,停止时将这个值置为FALSE,在OnDraw中检查这个值,如果发现是FALSE则再次启动定时器就行了。

3. 插入大的GIF时滚动条不能滚动

    这个问题是不能容忍的,当RichEdit高度很小,而插入的GIF高度超过RichEdit的高度时,垂直滚动条出现,这时发现向下滚动是没有任何作用的,滚动条只会不停地滚动又还原,根本不能滚动。原因是由于FireViewChange造成的,每次FireViewChange触发的OnDraw得到的RECT都是对象被插入时的RECT,所以导致不能滚动。这个问题也是能解决的,就是不使用FireViewChange,而使用InvalidateRect,为了避免动画闪烁,首先得将RichEdit设成背景透明,再响应WM_ERASEBKGND消息绘制白色背景,要使用这个函数,就必须得到容器窗口的句柄和需要绘制的RECT,句柄好得到,可以在创建对象时传入并保存,也可使用IOleInPlaceSite::GetWindow得到;RECT同样有两种方法得到,第一种前面说过,只要对象进入可视区,OnDraw就会被触发,可以将里面的RECT保存下来,可能你会觉得这个RECT不一定正确,那么还有一种,同样使用IOleInPlaceSite接口的GetWindowContext,第三个参数就是需要绘制的RECT。

    这样问题就解决了吗?猛然一看,还真解决了,但细心一点就会发现,还有更严重的问题!当插入一个动画后,换行,输入文字,最好每一行输入的文字宽度超过动画的宽度,不停地输入,直到动画变得不可见,这时你会发现刚才插入动画的位置的文本变粗了!原因是:RichEdit背景透明了,而InvalidateRect指定不重绘背景,动画插入位置上是文字,没有擦除背景的情况下文字被不断地重绘,导致变粗的现象;造成这个问题的根本原因就是GetWindowContext获得的RECT不正确!虽然动画不可见了,但仍然能获取到一个合法的RECT,即上一次动画显示处的RECT,这不就是OnDraw中的RECT吗,没错,就是它!所以前面还怀疑OnDraw中保存的RECT不正确的同学现在不用怀疑了,它和GetWindowContext得到的RECT完全一样,所以不需要使用GetWindowContext了(这里说这么多保存这个RECT是有目的的,慢慢看后面就会知道);当然,也可以尝试InvalidateRect时重绘背景,但是会发现动画闪烁特别厉害。


    造成所有上面这些问题的根本原因是每一个对象都单独使用了一个定时器,无论对象是否可见都在更新动画!如果不出现上面的这些问题,可能还不会发现这样做的效率有多低!当插入成千上万的动画时,CPU使用不知道会多高。那么如何做才能最大限度地提高效率,并解决这些问题呢?那就是只刷新可视区域的动画!

    估计有的同学会质疑了,说得轻巧,有那么容易知道一个对象是否在可视区就好了!的确,这也是我这些天来一直苦苦思索的问题,黄天不负有心人,总算找到了一种方法:IRichEditOle里面有GetObject,根据对象索引可以获取一些对象的信息,包括CLSID、IOleObject指针和CP(Char Position,字符索引),怎么根据这些东西来判断对象是否可见呢,当然,仅有这些东西是不能判断的,但里面的CP很有用!RichEdit有一个成员函数叫做CharFromPos,可以根据客户区中的点来得到这个点对应的字符索引,可能有的同学看出点眉目了,没错,我们可以获取客户区的RECT,得到左上角和右下角的点,再获取这两个点对应的字符索引,不就得到了可视区的最小和最大字符索引了吗,再判断对象的CP是否在这个区间内就可知道对象是否可见了。

    不过GetClientRect得到的区域稍大,因为RichEdit有个成员函数叫做SetRect,可以设置文本区域的RECT,所以应该使用GetRect得到正确的矩形区域。可能有的同学会怀疑了,可视区最后一行没有满怎么办,右下角就没有东西,得到的字符索引正确吗?答案是肯定的,无论右下角是否有文本,得到的都是可见区域最大的字符索引,如果右下角没有字符,那获得的字符索引就是这一行最后一个字符的字符索引,问题解决了!

     等等,GetObject第一个参数是对象索引,我们怎么知道一个对象的索引呢,这还不好办啊,直接for不就行了,从第0个开始遍历,直到找到自己就行了!

     有人会骂了,说了这么多,虽然解决了如何判断对象是否可见的问题,但又引入新的问题,每次都遍历,那插入成千上万的动画,每个动画的定时器都去遍历,你可知道动画刷新频率是很高的,通常100毫秒,估计这效率比直接刷新更低吧!没办法,谁让那些问题那么烦人呢!

     别忙着开骂,还没说完呢,遍历效率低,我们就来提高效率,你可以测试一下,IRichEditOle中存储的对象是按照字符索引从小到大进行排序的,比如先插入个动画1,再在这个动画前插入另一个动画2,这时候你GetObject传入索引为0时,会发现得到的对象是动画2,所以可以证明对象的确是排好序的,别告诉我读到这里你还不知道怎么提高效率,二分查找啊,比如可视区的最小字符索引是cpMin,最大字符索引为cpMax,那就使用二分查找找到第一个字符索引大于或等于cpMin的对象索引,再从这个索引开始向后遍历,找到字符索引大于cpMax时停止遍历,效率不是就提高了吗。

    说了这么多,还是得每个对象自己在定时器处理函数中找到自己的字符索引,即使找的次数少得多了,但还是那句老话,成千上万的对象还是需要查找很多次,特别是当可视区较大时,找的次数会很大,这种

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值