当前位置:职场发展 > WindowLess RichEdit 实现QQ聊天窗口的气泡效果,设计思路和方法。

WindowLess RichEdit 实现QQ聊天窗口的气泡效果,设计思路和方法。

  • 发布:2023-09-29 06:58



作为一个开始使用WindowUi之旅的程序员,他对一些新的UI效果的实现还是相当好奇的。

现在最流行的UI设计开发不再是原生windows控件,也不再是directui控件,而是更倾向于h5编写界面。

但是我们公司产品的UI是我亲自实现的directui库。由于个人兴趣,项目中无意中用到了我一时兴起实现的库。现在就算想改成h5,也很难下手。 。

Directui的灵活性不如h5,功能也远不如h5。 directui的每个控件和功能都必须自己实现。对于h5,你只需要找到一个浏览器shell即可。它简单又快速。

但是directui具有内存占用小、运行效率高的优点,所以directui库一直没有被取代。


以上都是废话。由于我个人还是比较喜欢使用directui来实现界面,所以我一直在维护directui库。作为一个高难度的RichEdit控件,一直很让人头疼。如何像qq一样灵活、完美,是我一直追求的。 ,但是网上的资料比较少,也没有比较好的文章和技术介绍。最近很好奇QQ中气泡效果的实现,因为QQ也是用windowsless richedit实现的聊天窗口,而且也用的是richedit2.0。我只是添加了一个额外的 dll 来进行一些扩展。我也用richedit2.0,但是无法实现。起泡效果很差。


刚刚实施,一些细节还没有调整,不过经过一些微调应该就可以实际应用了。

对应QQ聊天窗口中的其他消息一般可以通过以下方法实现。


目前我实现的是,在原来的气泡上通过复制粘贴修改,气泡可以正常放大,但是输入字符和撤消气泡就会出现异常。要解决这个问题,需要拦截输入并将其转为插入,然后自己实现撤消。不过由于我暂时没有这个需求,所以暂时不会扩展。


也许很多人还有其他方法来实现。如果这个东西用h5来实现,可能就是儿戏。这种实现并不困难。如果有人还没有实现,你可以参考一下。


先来看看效果图:

{img_1:aHR0cHM6Ly9pbWctYmxvZy5jc2RuLm5ldC8yMDE3MDgxNDExMjU0NzA2NT93YXRlcm1hcmsvMi90ZXh0L2FIUjBjRG92TDJKc2IyY3VZM05rYmk1dVpYUXZi


于是我开始搜集资料,查看别人的demo,查msdn,翻接口文​​档。


最后发现实现其实还是比较简单的。我原来想到的实现方法不可靠,效率低下。本来担心会卡住,但是代码实现后发现还是很快的。也许我低估了CPU


实现气泡的主要目的是计算混合图形和文本数据的长度和宽度。


计算身高是比较流行的方法,没有其他特殊渠道。好像是第一行第一个字符的左上角顶点和下一行最后一个字符的左上角顶点的差,

宽度 只需计算最长线的宽度即可。这里有一处异常。如果一行太长,自动换行,那么所选行最后一个字符上的光标将定位到下一行的行首,而不是当前行的末尾,所以这里需要注意。下面我将计算图贴出来。文本数据范围代码

void CxRichEx::CalAsLeft(StBubbleInfo & sbi,CxRect const& xRect,CxSCROLLINFO const& sli){//背景项应有的宽度和高度 int iHigh = 0;int iWidth = 0;//计算文字高度 POINT ps = xRichEdit.CxPosFromChar(sbi.lStart);POINT pe = xRichEdit.CxPosFromChar(sbi. lEnd+1 );iHigh = pe.y-ps.y;//计算文本宽度 LONG ls = xRichEdit.GetLineFromIndex(sbi.lStart);LONG le = xRichEdit.GetLineFromIndex(sbi.lEnd);if(ls!=le ) {//如果是多行文本//计算第一个字符的x坐标 xRichEdit.CxSetSel(sbi.lStart,sbi.lStart);int iLeft = xRichEdit.GetCurTargetX();for (int i=ls; i<=le;i++) {int iIndex = xRichEdit.GetLineIndex(i);int iLineLen = xRichEdit.GetLineLenght(iIndex);if(iLineLen==0)continue;int iLast = iIndex+iLineLen;xRichEdit.CxSetSel(iLast,iLast);int iRight = xRichEdit.GetCurTargetX();if(iRight<=iLeft) {iWidth = xRect.Width();break;}else {iWidth = CxMax(iRight-xRect.left,iWidth);}}}else {//如果为单行文本//宽度直接计算得出xRichEdit.CxSetSel(sbi.lStart,sbi.lStart);iWidth = xRect.right-xRichEdit.GetCurTargetX();}//计算控件位置CxRect rcRelative;rcRelative.left = xRect.left;www.sychzs.cn  = www.sychzs.cn+sli.nPos;//调整控件位置www.sychzs.cn -= www.sychzs.cn;iHigh += www.sychzs.cn+xRcFlate.bottom;iWidth = CxMin(xRect.Width(),iWidth+xRcFlate.left+xRcFlate.right);sbi.pBubble->SetParentAdjust( CxRect(0,-sli.nPos,0,0),false);sbi.pBubble->SetRelPosition(rcRelative,false);sbi.pBubble->ClearRect();sbi.pBubble->SetCtrlSize (CxSize(iWidth,iHigh ));sbi.pBubble->SetAdjust(true);
}void CxRichEx::CalAsRight(StBubbleInfo& sbi,CxRect const& xRect,CxSCROLLINFO const& sli){//背景项应有的宽度和高度 int iHigh = 0;int iWidth = 0;//计算文字高度 POINT ps = xRichEdit.CxPosFromChar(sbi.lStart);POINT pe = xRichEdit.CxPosFromChar(sbi. lEnd+1 );iHigh = pe.y-ps.y;//计算文本宽度 LONG ls = xRichEdit.GetLineFromIndex(sbi.lStart);LONG le = xRichEdit.GetLineFromIndex(sbi.lEnd);if(ls!=le ) {//多行文本//计算第一个字符的x坐标 for (int i=ls;i<=le;i++) {int iIndex = xRichEdit.GetLineIndex(i);xRichEdit.CxSetSel(iIndex,iIndex);int iLeft = xRichEdit.GetCurTargetX();iWidth = CxMax(xRect.right-iLeft,iWidth);}}else {//如果为单行文本//宽度直接计算得出xRichEdit.CxSetSel(sbi.lStart,sbi.lStart);iWidth = CxMax(xRect.right- xRichEdit.GetCurTargetX(),iWidth);}//计算控件位置CxRect rcRelative;www.sychzs.cn   = www.sychzs.cn+sli.nPos;//调整控件位置www.sychzs.cn -= www.sychzs.cn;iHigh += www.sychzs.cn+xRcFlate.bottom;iWidth = CxMin(xRect.Width(),iWidth+xRcFlate.left+xRcFlate.right);rcRelative.left = -iWidth;sbi.pBubble->SetParentAdjust(CxRect(0,-sli.nPos,0,0),false);sbi.pBubble- >SetRelPosition( rcRelative,false);sbi.pBubble->ClearRect();sbi.pBubble->SetCtrlSize(CxSize(iWidth,iHigh));sbi.pBubble->SetAdjust(true);
}void CxRichEx::RejustBubble(StBubbleInfo& sbi,CxRect const& xRect,CxSCROLLINFO const& sli){//获取原来选择的位置 LONG lsStart = 0;LONG lsEnd = 0;xRichEdit.CxGetSel(lsStart,lsEnd);//判断是右对齐还是左对齐 xRichEdit.CxSetSel(sbi.lStart,sbi.lEnd); PARAFORMAT2 pf;ZeroMemory(&pf,sizeof(pf));pf.dwMask = PFM_ALIGNMENT|PFM_STARTINDENT|PFM_RIGHTINDENT;xRichEdit.GetParaFormat(pf);sbi.bLeftAlignment = (pf.wAlignment==PFA_LEFT);sbi.lIndent = pf.dxStartIndent ;sbi.rIndent = pf.dxRightIndent;if(sbi.bLeftAlignment)CalAsLeft(sbi,xRect,sli);else CalAsRight(sbi,xRect,sli);//设置回原来选中区域 xRichEdit.CxSetSel(lsStart,lsEnd );
}


计算完长宽后,定位就是在这个位置插入一个控件,插入一个自己实现的TAB容器。 StBubbleInfo 存储字符气泡的起始和结束位置、控制指针和其他必要信息。然后调整控件的位置,调整时机:richedit的位置和大小变化、滚动条变化、内容变化(计算受影响的气泡的位置和大小)。


这里的CxRichedit是我基于Windowless Richedit的封装,内部封装是基于ITextServices和ITextHost。你应该可以根据调用接口名称知道我的函数的内部实现。

因为原来项目的时序不合适,导致我的控制库内部实现代码有点混乱,而且消息回调使用了boost库和loki库的函子,现在很少有人会用了。 。

所以其他代码就不贴了。你根据函数名自己实现即可。

这里我将文本缩进60像素并插入自己实现的TAB。在外部插入气泡后,我将获得 TAB 指针并进行任何个性化设置。所有控件都可以插入到 TAB 中,也可以响应按键。等消息。



bubble实现的核心代码:

RichEx.h

#ifndef __CxRichEx_h__
#define __CxRichEx_h__class CxRichEx;
类 CxRichEdit;类 CxCheckBubble
{
public:CxCheckBubble(CxRichEdit& re,CxRichEx& xree,bool bBb);~CxCheckBubble();private:long lStart;bool bBubble;CxRichEx& xRichEx;CxRichEdit& xRichedit;
};CxRejustBubble 类
{
public:CxRejustBubble(CxRichEdit& re,CxRichEx& xree,bool bBb);~CxRejustBubble();private:long lStart;long lEnd;LONG lNowLong;bool bBubble;CxRichEx& xRichEx;CxRichEdit& xRichedit;
};CxRichEx 类:公共 IRichEx
{public:CxRichEx(CxRichEdit& re);~CxRichEx();/*** @添加气泡信息*/void AddBubble(StBubbleInfo& sbi);/*** @绘制气泡*/void DrawBubble(GDIPlus& graph,RECT const& rcHost,RECT const& rcClip);/*** @重新调整所有气泡位置*/void RejustBubble();/*** @重新调整指定气泡位置*/void RejustBubble(StBubbleInfo& sbi,CxRect const& xRect,CxSCROLLINFO const& sli) ;/* ** @重新调整指定气泡位置*/void ScrollBubble();/*** @重新调整指定气泡位置*/void ScrollBubble(StBubbleInfo const& sbi,CxSCROLLINFO const& sli);/*** @设置下次是否重新计算Position*/void SetAdJust(bool bAdjust);/*** @调整选中的段落*/void RejustSelect();/*** @内容已经改变*/void ContentChange(long lStart,长 lEnd,长 lDiff); /*** @查找气泡对象* 参数为气泡索引*/CxTab* GetBubble(int iIndex);/*** @查找气泡对象* 参数为气泡索引*/StBubbleInfo* GetBubbleInfo(int iIndex);/* ** @设置控件在四个方向上分别放大像素*/void SetFlate(CxRect const& rcFlate);/*** @获取控件在四个方向上分别放大像素*/CxRect const& GetFlate(); private:/*** @左对齐计算控件大小*/void CalAsLeft(StBubbleInfo& sbi,CxRect const& xRect,CxSCROLLINFO const& sli);/*** @右对齐计算控件大小*/void CalAsRight(StBubbleInfo& s)bi,CxRect const & xRichEdit;};#endif


RichEx.cpp

#include“CxUiLib.h”
#include“CxRichEx.h”/************************************************ **********************************
* 添加者:support@www.sychzs.cn
* 描述:
*
****************************************************** * **********************/
CxCheckBubble::CxCheckBubble(CxRichEdit& re,CxRichEx& xree,bool bBb)
:xRichedit(重新)
,xRichEx(xree)
, b气泡(bBb)
{if(!bBubble)return;lStart = xRichedit.GetTextLength();
}
CxCheckBubble::~CxCheckBubble()
{if(!bBubble)return;long lEnd = xRichedit.GetTextLength();xRichedit.CxInsertText(lEnd,_T("\r\n"),true);StBubbleInfo sbi;sbi.lStart = lStart;sbi.lEnd = lEnd ;xRichEx.AddBubble(sbi);
}
/******************************************************** ***** *************************
* 添加者:support@www.sychzs.cn* 描述:
*
****************************************************** **********************/
CxRejustBubble::CxRejustBubble(CxRichEdit& re,CxRichEx& xree,bool bBb)
: xRichedit(重新)
, xRichEx(xree)
, b气泡(bBb)
{if(bBubble) {xRichedit.CxGetSel(lStart,lEnd);lNowLong = xRichedit.GetTextLength();}
}CxRejustBubble::~CxRejustBubble()
{if(bBubble) {long lNewLong = xRichedit.GetTextLength();xRichEx.ContentChange(lStart,lEnd,lNewLong-lNowLong);}
}/**************************************************** **************************
* 添加者:support@www.sychzs.cn
* 描述:
*
****************************************************** **********************/
StBubbleInfo::StBubbleInfo()
: l开始(0)
, l结束(0)
, pBubble(NULL)
, bLeftAlignment(true)
, l缩进(0)
, r缩进(0)
{}/**************************************************** **************************
* 添加者:support@www.sychzs.cn
* 描述:
*
****************************************************** **********************/CxRichEx::CxRichEx(CxRichEdit& re)
: xRichEdit(重新)
{}
CxRichEx::~CxRichEx()
{发布();
}颜色cv[4]={颜色(255,255,255,0),颜色(255,185,185,185),颜色(255,0,255,255),颜色(255,255,0,255)};
//添加气泡信息
void CxRichEx::AddBubble(StBubbleInfo& sbi)
{static int i=1;i++;CxTab* pBubble = new CxTab();pBubble->SetParent(xRichEdit.GetWnd(),xRichEdit.GetPaintCenter());pBubble->CxSetBkColor(cv[i%4],cv[ i%4],cv[i%4],cv[i%4]);StBubbleInfo* pStBbInfo = new StBubbleInfo();*pStBbInfo = sbi;pStBbInfo->pBubble = pBubble;lBubbleInfo.push_back(pStBbInfo);CxRect xRect = xRichEdit.GetContentRect();CxSCROLLINFO sli;xRichEdit.CxGetScrollInfo(sli,CXSB_VERT);RejustBubble(*pStBbInfo,xRect,sli);
}void CxRichEx::DrawBubble(GDIPlus& graph,RECT const& rcHost,RECT const& rcClip)
{for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);pBbi ->pBubble->CxDrawSelf(graph,rcHost,rcClip);}}void CxRichEx::RejustBubble()
{CxRect xRect = xRichEdit.GetContentRect();CxSCROLLINFO sli;xRichEdit.CxGetScrollInfo(sli,CXSB_VERT);for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end() ;_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);RejustBubble(*pBbi,xRect,sli);}
}void CxRichEx::CalAsLeft(StBubbleInfo & sbi,CxRect const& xRect,CxSCROLLINFO const& sli){//背景项应有的宽度和高度 int iHigh = 0;int iWidth = 0;//计算文字高度 POINT ps = xRichEdit.CxPosFromChar(sbi.lStart);POINT pe = xRichEdit.CxPosFromChar(sbi. lEnd+1 );iHigh = pe.y-ps.y;//计算文本宽度 LONG ls = xRichEdit.GetLineFromIndex(sbi.lStart);LONG le = xRichEdit.GetLineFromIndex(sbi.lEnd);if(ls!=le ) {//如果是多行文本//计算第一个字符的x坐标 xRichEdit.CxSetSel(sbi.lStart,sbi.lStart);int iLeft = xRichEdit.GetCurTargetX();for (int i=ls; i<=le;i++) {int iIndex = xRichEdit.GetLineIndex(i);int iLineLen = xRichEdit.GetLineLenght(iIndex);if(iLineLen==0)continue;int iLast = iIndex+iLineLen;xRichEdit.CxSetSel(iLast,iLast);int iRight = xRichEdit.GetCurTargetX();if(iRight<=iLeft) {iWidth = xRect.Width();break;}else {iWidth = CxMax(iRight-xRect.left,iWidth);}}}else {//如果为单行文本//宽度直接计算得出xRichEdit.CxSetSel(sbi.lStart,sbi.lStart);iWidth = xRect.right-xRichEdit.GetCurTargetX();}//计算控件位置CxRect rcRelative;rcRelative.left = xRect.left;www.sychzs.cn  = www.sychzs.cn+sli.nPos;//调整控件位置www.sychzs.cn -= www.sychzs.cn;iHigh += www.sychzs.cn+xRcFlate.bottom;iWidth = CxMin(xRect.Width(),iWidth+xRcFlate.left+xRcFlate.right);sbi.pBubble->SetParentAdjust( CxRect(0,-sli.nPos,0,0),false);sbi.pBubble->SetRelPosition(rcRelative,false);sbi.pBubble->ClearRect();sbi.pBubble->SetCtrlSize (CxSize(iWidth,iHigh ));sbi.pBubble->SetAdjust(true);
}void CxRichEx::CalAsRight(StBubbleInfo& sbi,CxRect const& xRect,CxSCROLLINFO const& sli){//背景项应有的宽度和高度 int iHigh = 0;int iWidth = 0;//计算文字高度 POINT ps = xRichEdit.CxPosFromChar(sbi.lStart);POINT pe = xRichEdit.CxPosFromChar(sbi. lEnd+1 );iHigh = pe.y-ps.y;//计算文本宽度 LONG ls = xRichEdit.GetLineFromIndex(sbi.lStart);LONG le = xRichEdit.GetLineFromIndex(sbi.lEnd);if(ls!=le ) {//多行文本//计算第一个字符的x坐标 for (int i=ls;i<=le;i++) {int iIndex = xRichEdit.GetLineIndex(i);xRichEdit.CxSetSel(iIndex,iIndex);int iLeft = xRichEdit.GetCurTargetX();iWidth = CxMax(xRect.right-iLeft,iWidth);}}else {//如果为单行文本//宽度直接计算得出xRichEdit.CxSetSel(sbi.lStart,sbi.lStart);iWidth = CxMax(xRect.right- xRichEdit.GetCurTargetX(),iWidth);}//计算控件位置CxRect rcRelative;www.sychzs.cn   = www.sychzs.cn+sli.nPos;//调整控件位置www.sychzs.cn -= www.sychzs.cn;iHigh += www.sychzs.cn+xRcFlate.bottom;iWidth = CxMin(xRect.Width(),iWidth+xRcFlate.left+xRcFlate.right);rcRelative.left = -iWidth;sbi.pBubble->SetParentAdjust(CxRect(0,-sli.nPos,0,0),false);sbi.pBubble- >SetRelPosition( rcRelative,false);sbi.pBubble->ClearRect();sbi.pBubble->SetCtrlSize(CxSize(iWidth,iHigh));sbi.pBubble->SetAdjust(true);
}void CxRichEx::RejustBubble(StBubbleInfo& sbi,CxRect const& xRect,CxSCROLLINFO const& sli){//获取原来选择的位置 LONG lsStart = 0;LONG lsEnd = 0;xRichEdit.CxGetSel(lsStart,lsEnd);//判断是右对齐还是左对齐 xRichEdit.CxSetSel(sbi.lStart,sbi.lEnd); PARAFORMAT2 pf;ZeroMemory(&pf,sizeof(pf));pf.dwMask = PFM_ALIGNMENT|PFM_STARTINDENT|PFM_RIGHTINDENT;xRichEdit.GetParaFormat(pf);sbi.bLeftAlignment = (pf.wAlignment==PFA_LEFT);sbi.lIndent = pf.dxStartIndent ;sbi.rIndent = pf.dxRightIndent;if(sbi.bLeftAlignment)CalAsLeft(sbi,xRect,sli);else CalAsRight(sbi,xRect,sli);//设置回原来选中区域 xRichEdit.CxSetSel(lsStart,lsEnd );
}void CxRichEx::ScrollBubble()
{CxSCROLLINFO sli;xRichEdit.CxGetScrollInfo(sli,CXSB_VERT);for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);ScrollBubble(*pBbi,sli);}
}void CxRichEx::ScrollBubble(StBubbleInfo const& sbi,CxSCROLLINFO const& sli)
{sbi.pBubble->SetAdjust(true);sbi.pBubble->SetParentAdjust(CxRect(0,-sli.nPos,0,0),false);
}void CxRichEx::Release()
{for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);SafeDelete(pBbi->pBubble);SafeDelete(pBbi);}lBubbleInfo.clear();
}CxTab* CxRichEx::GetBubble(int iIndex)
{CxTab* pTab = NULL;for (std::list ::iterator _it=lBubbleInfo.begin();!pTab&&_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);if(iIndex>=pBbi->lStart&&iIndex<=pBbi->lEnd)pTab = pBbi->pBubble;}return pTab;
}StBubbleInfo* CxRichEx::GetBubbleInfo(int iIndex)
{StBubbleInfo* pResult = NULL;for (std::list ::iterator _it=lBubbleInfo.begin();!pResult&&_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);if(iIndex>=pBbi->lStart&&iIndex<=pBbi->lEnd)pResult = pBbi;}return pResult;
}void CxRichEx::SetAdJust(bool bAdjust)
{for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);IFISNULLCONTINUE(pBbi->pBubble);pBbi->pBubble->SetAdjust(bAdjust);}
}void CxRichEx::SetFlate(CxRect const& rcFlate)
{xRcFlate = CxRectTurnx(rcFlate);
}CxRect const& CxRichEx::GetFlate()
{return xRcFlate;
}void CxRichEx::ContentChange(long lStart,long lEnd,long lDiff)
{bool bHaveFindIn = false;CxRect xRect = xRichEdit.GetContentRect();CxSCROLLINFO sli;xRichEdit.CxGetScrollInfo(sli,CXSB_VERT);for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);if(pBbi->lEndlEnd) {if(lStartlStart)pBbi->lStart += lDiff;pBbi->lEnd += lDiff;RejustBubble(*pBbi,xRect,sli);}}
}
/******************************************
如果选中范围与节点有交集则调整
******************************************/
void CxRichEx::RejustSelect()
{LONG lStart = 0;LONG lEnd = 0;xRichEdit.CxGetSel(lStart,lEnd);CxRect xRect = xRichEdit.GetContentRect();CxSCROLLINFO sli;xRichEdit.CxGetScrollInfo(sli,CXSB_VERT);for (std::list ::iterator _it=lBubbleInfo.begin();_it!=lBubbleInfo.end();_it++) {StBubbleInfo* pBbi = (StBubbleInfo*)*_it;IFISNULLCONTINUE(pBbi);if(lEndlStart)continue;if(lStart>pBbi->lEnd)continue;RejustBubble(*pBbi,xRect,sli);}xRichEdit.CxSetSel(lStart,lEnd);
}




相关文章

最新资讯