實現一種快速查找Richedit中可見區域內OLE對象的方法


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對象.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM