最近工作上比较忙,加之编码任务较多,没来得及继续之前的讲解。抽出时间把这最重要的一部分东西做个阐述。行文以基本的编程思维及个人思考过程为线索。
众所周知,RichEdir强大在于其图文混排(在这里不跟Word、HTML比),其中的图替换为动态图的核心问题就归结于如何高效刷新。我们知道GDI操作是最消耗CPU的,所以刷新整个RichEdit窗口是不可取的,其副作用会导致更严重的闪烁问题。解决问题的思路很简单:类似于拖拽时候在屏幕绘制异或线,我们的动画重绘时不请求RichEdit,而直接在其窗口的DC上绘制当前动画帧,此时缺少是如何确定该OLE的位置,这个是所有问题的关键。先看下面这幅图:
假定1-5全部都是GIF图片,非GIF可以暂时无视,这个后面大家会非常清楚如何处理。在这个过程中,2不见了,而4是新出现的。对于4新出现时,RichEdit自身肯定会触发其:
Draw( DWORD dwDrawAspect, LONG lindex, void* pvAspect, DVTARGETDEVICE* ptd, HDC hicTargetDev, HDC hdcDraw, LPCRECTL prcBounds, LPCRECTL prcWBounds, BOOL (__stdcall *pfnContinue)(DWORD_PTR dwContinue), DWORD_PTR dwContinue)
这个时候,我们知道新的GIF图片进入可视区,可以把它添加到集合中。对于2的动画触发时间到来时,我们可以确定其位置且与可视区比对,发现其不再可视区,则从集合中移除。这样就可以得到一个接近于(略大于)当前视口中的动画控件集合,当有新的动画触发时间到来时,我们可以先检查其是否在可视区,如果不在则不用GDI操作,仅仅更新其当前帧。当然这些工作你也可以不做,但是在动画控件数量大的时候效率可能略有下降,主要是查找的过程(索引、位置)比较耗时。
如何确定一个OLE的位置呢?由于我们插入OLE都使用了REO_BELOWBASELINE标志,也就是跟当前行的底部对齐,所以OLE左下角位置的精确度对我们来说很重要。看下图:
假设图中黑框是一个OLE对象,其字符索引为CPN,假定第N+1行的第一个字符索引为CPN1,那么OLE左下角坐标={PosFromChar(CPN).x, PosFromChar(CPN1).y },PosFromChar这个是RichEdit提供的。问题的关键是最后一行怎么计算?此时没有第N+1行。对于这种特殊情况,主要是Y坐标的计算,可以这样考虑:Y=RichEdit内容高度-滚动条位置。猜测: 计算内容高度可能比较耗时,故QQ的聊天消息显示部分强制在底部加了一行,以避免这种情况出现。
得到左下角位置以后,可能你会觉得就万事大吉了。错!还有一个关键点!我们可以通过OLE的接口GetExtent得到其大小,然而这个大小没有考虑缩放比例,所以你需要根据当前缩放比例进行计算,而这个计算牵扯到浮点数运算,过程中的来回不仅麻烦而且不精确,所以OLE的可视大小要想非常精确是不能通过计算来的。我们前面知道OLE绘制的时候会传入可视范围,假如我们保存下来是不是就可以解决问题了呢?当然,显然,你可以试试!
这些问题主要原因是RichEdit的很多接口方法没有暴露,而Win8的SDK会做重大升级,很多之前的问题都会变成不是问题,或许还会引起更多的新特性,但是动画本身的逻辑还是需要自己实现,或者会简单许多,至于多少我还尚不清楚,但是目前来看这种方案效率足够!
到了这里,核心技术应该大白天下,整个过程,我追求了位置的精准度,并据此获得最小可视集合进行刷新优化。
最新SDK&Demo,参见:http://code.google.com/p/im-solution/。希望你会喜欢!