Richedit是一個OLE容器,使用Richedit來顯示IM聊天內容時,通常使用OLE對象來實現在Richedit中播放表情動畫。
觸發表情的繪制有兩種途徑:
1、來自Richedit的刷新消息。
2、來自表情動畫定時器的刷新消息。
要刷新表情的顯示首先需要知道表情的顯示位置。
第一種刷新過程中,繪制消息參數里已經給出繪制位置,直接在指定的位置繪制即可。
但是表情主動刷新時如何獲取表情的顯示位置確是一個問題。
網上有不少代碼演示了如何獲通過枚舉Richedit中的OLE對象獲取表情的代碼。
這些代碼估計很多都來自一個共同的祖先:
為了獲得一個OLE對象的顯示位置,都需要從當前Richedit中逐個枚舉OLE對象,直接找到這個觸發刷新的OLE對象為止。
再通過該OLE對象的字符索引計算出顯示位置。
當Richedit中插入的OLE對象相對較少時,這種方法還可以滿足需要,但是如果表情成千上萬,這樣的查詢方式必然導致CPU占用飆升。
雖然要獲得一個OLE對象的顯示位置,目前看來只有先獲得該對象在Richedit中的插入位置,但是有沒有更簡單的方法先判斷一個OLE對象是否在可見范圍內呢?
要解決這個問題,首先需要從Richedit中獲取可見字符的范圍。
首先通過EM_GETFIRSTVISIBLELINE+EM_LINEINDEX獲得第一個可見行的第一個字符的索引號。
通過EM_GETRECT+EM_CHARFROMPOS可以獲得最后一個可見字符的索引號。
如此我們就可以獲得可見字符的范圍。
表情動畫觸發時我們只知道這個OLE對象的指針,但並不知道這個OLE對象的字符索引。
要獲得顯示位置,首先需要判斷這個OLE對象是不是在可見范圍。
為此我可以采用兩分法快速找出所有在可見范圍內的OLE對象,再將當前的OLE對象和可見范圍內的OLE對象逐個比較,進而判斷該對象是否可見,並最終獲得顯示位置。
相比網上流傳的方法,這種方法在查找一個OLE對象的顯示位置時只需要和可見范圍內的OLE對象逐個比較,比較次數通常是非常小的,因此完全不用擔心CPU占用問題。
下面是用源代碼,希望對那些被這個問題困擾的人有些幫助。
LONG GetOleCP(IRichEditOle *pOle, int iOle) { REOBJECT reobj={0}; reobj.cbStruct=sizeof(REOBJECT); pOle->GetObject(iOle,&reobj,REO_GETOBJ_NO_INTERFACES); return reobj.cp; } //find first Ole Object in char range of [cpMin,cpMax) int FindFirstOleInrange(IRichEditOle *pOle, int iBegin,int iEnd,int cpMin,int cpMax) { if(iBegin==iEnd) return -1; int iMid = (iBegin + iEnd)/2; LONG cp = GetOleCP(pOle,iMid); if(cp < cpMin) { return FindFirstOleInrange(pOle,iMid+1,iEnd,cpMin,cpMax); }else if(cp >= cpMax) { return FindFirstOleInrange(pOle,iBegin,iMid,cpMin,cpMax); }else { int iRet = iMid; while(iRet>iBegin) { cp = GetOleCP(pOle,iRet-1); if(cp<cpMin) break; iRet --; } return iRet; } } //find Last Ole Object in char range of [cpMin,cpMax) int FindLastOleInrange(IRichEditOle *pOle, int iBegin,int iEnd,int cpMin,int cpMax) { if(iBegin==iEnd) return -1; int iMid = (iBegin + iEnd)/2; LONG cp = GetOleCP(pOle,iMid); if(cp < cpMin) { return FindLastOleInrange(pOle,iMid+1,iEnd,cpMin,cpMax); }else if(cp >= cpMax) { return FindLastOleInrange(pOle,iBegin,iMid,cpMin,cpMax); }else { int iRet = iMid; while(iRet<(iEnd-1)) { cp = GetOleCP(pOle,iRet+1); if(cp>=cpMax) break; iRet ++; } return iRet; } } int CGifSmileyCtrl::GetObjectPos( HWND hWnd) { if ( !hWnd ) return -1; IRichEditOle * ole=NULL; if (!::SendMessage(hWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&ole)) return -1; int iRet = -1; //獲得可見字符范圍 int iFirstLine = SendMessage(hWnd,EM_GETFIRSTVISIBLELINE,0,0); RECT rcView; SendMessage(hWnd,EM_GETRECT,0,(LPARAM)&rcView); POINT pt={rcView.right+1,rcView.bottom-2}; LONG cpFirst = SendMessage(hWnd,EM_LINEINDEX,iFirstLine,0); LONG cpLast = SendMessage(hWnd,EM_CHARFROMPOS,0,(LPARAM)&pt); //采用兩分法查找在可見范圍中的OLE對象 int nCount=ole->GetObjectCount(); int iFirstVisibleOle = FindFirstOleInrange(ole,0,nCount,cpFirst,cpLast); if(iFirstVisibleOle!=-1) { int iLastVisibleOle = FindLastOleInrange(ole,iFirstVisibleOle,nCount,cpFirst,cpLast); ATLASSERT(iLastVisibleOle!=-1); for(int i=iFirstVisibleOle;i<=iLastVisibleOle;i++) { REOBJECT reobj={0}; reobj.cbStruct=sizeof(REOBJECT); ole->GetObject(i,&reobj,REO_GETOBJ_NO_INTERFACES); if (reobj.clsid==__uuidof(CGifSmileyCtrl) && ((CGifSmileyCtrl*)reobj.dwUser)==this) { iRet = i; break; } } } ole->Release(); return iRet; }
上面CGifSmileyCtrl代表一個表情OLE對象.
