作为一个由WindowUi入门开始程序员之路的程序员还是比较好奇一些新UI效果实现的。
时下最流行的UI设计和开发,已经不再是原生的windows控件,也不是directui控件,更多的偏向于h5写界面了。
但是我们公司产品的UI是我个人实现的一个directui库,由于个人兴趣当初一时兴起实现的库阴差阳错的用于项目中了,现在即使想换成h5的也很难下手了。
directui对比h5而言不太灵活,功能远没有h5多,directui各个控件和功能都要自己实现,h5只要找一个浏览器壳就可以了简单快速。
但是directui内存占用少,运行效率高是优势,所以一直没有换掉directui库。
以上是废话,基于我个人依然偏向于用directui实现界面,也就一直在维护directui库,作为较难的richedit控件一直比较头疼,如何能像qq一样的灵活和完善也是我一直追寻的,但是网上的一些资料比较少,也没有什么相对好的文章和技术介绍。 最近好奇qq的气泡效果的实现,因为qq也是用windowsless richedit实现的聊天窗口,而且也是用的richedit2.0 只是加了一个附加dll做了一些扩展,我也是用richedit2.0了啊,实现不了那个气泡效果 就很自卑啊。
刚实现好,有一些细节还没调整,但是微调一下应该就可以实际应用了。
相应的QQ聊天窗口的其他消息 用如下方法大致也可以实现。
这里我目前实现的,通过复制粘贴的方式在原有气泡上修改气泡也可以正常变大,但是输入字符和撤销气泡会异常,要想解决这个需要拦截输入变成插入,自己实现撤销即可,由于我目前没有这个需求暂时不进行扩展了。
可能很多人都有其他办法可以实现了吧,这玩意如果用h5实现可能是小儿科了 这种实现也不难 如果有人还没有实现办法可以参考一下。
先来一个效果图吧:
于是开始了搜集资料 查看别人的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;
rcRelative.top = ps.y-xRect.top+sli.nPos;
//调整控件位置
rcRelative.top -= xRcFlate.top;
iHigh += xRcFlate.top+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;
rcRelative.top = ps.y-xRect.top+sli.nPos;
//调整控件位置
rcRelative.top -= xRcFlate.top;
iHigh += xRcFlate.top+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);
}
这里的CxRichedit是我基于Windowless Richedit的封装,内部基于ITextServices 和ITextHost的封装,大家根据调用接口名 应该可以知道我函数内部的实现了。
由于当初项目的需要 使用的时机不太合适,所以我控件库内部实现代码有一些比较混乱,而且消息回调使用了boost库和loki库的仿函数,现在很少有人能用得上了。
所以其他代码不发了,大家根据函数名自己实现即可。
这里 我把文本进行缩进60像素 然后插入一个我自己实现的TAB,外部插入气泡后获取TAB指针,进行任何个性化设置,所有的控件都可以插入到TAB中,同样也可以响应按键等消息。
气泡实现的核心代码:
RichEx.h
#ifndef __CxRichEx_h__
#define __CxRichEx_h__
class CxRichEx;
class CxRichEdit;
class CxCheckBubble
{
public:
CxCheckBubble(CxRichEdit& re,CxRichEx& xree,bool bBb);
~CxCheckBubble();
private:
long lStart;
bool bBubble;
CxRichEx& xRichEx;
CxRichEdit& xRichedit;
};
class CxRejustBubble
{
public:
CxRejustBubble(CxRichEdit& re,CxRichEx& xree,bool bBb);
~CxRejustBubble();
private:
long lStart;
long lEnd;
LONG lNowLong;
bool bBubble;
CxRichEx& xRichEx;
CxRichEdit& xRichedit;
};
class CxRichEx
: public 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);
/**
* @设置下次是否重新计算位置
*/
void SetAdJust(bool bAdjust);
/**
* @调整选中的段落
*/
void RejustSelect();
/**
* @内容发生了变化
*/
void ContentChange(long lStart,long lEnd,long 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& sbi,CxRect const& xRect,CxSCROLLINFO const& sli);
/**
* @释放控件空间
*/
void Release();
private:
CxRect xRcFlate; //标记四个方向各自放大像素
std::list lBubbleInfo;
CxRichEdit& xRichEdit;
};
#endif
RichEx.cpp
#include "CxUiLib.h"
#include "CxRichEx.h"
/**************************************************************************
* Add by: [email protected]
* Describe:
*
**************************************************************************/
CxCheckBubble::CxCheckBubble(CxRichEdit& re,CxRichEx& xree,bool bBb)
: xRichedit(re)
, xRichEx(xree)
, bBubble(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);
}
/**************************************************************************
* Add by: [email protected]
* Describe:
*
**************************************************************************/
CxRejustBubble::CxRejustBubble(CxRichEdit& re,CxRichEx& xree,bool bBb)
: xRichedit(re)
, xRichEx(xree)
, bBubble(bBb)
{
if(bBubble) {
xRichedit.CxGetSel(lStart,lEnd);
lNowLong = xRichedit.GetTextLength();
}
}
CxRejustBubble::~CxRejustBubble()
{
if(bBubble) {
long lNewLong = xRichedit.GetTextLength();
xRichEx.ContentChange(lStart,lEnd,lNewLong-lNowLong);
}
}
/**************************************************************************
* Add by: [email protected]
* Describe:
*
**************************************************************************/
StBubbleInfo::StBubbleInfo()
: lStart(0)
, lEnd(0)
, pBubble(NULL)
, bLeftAlignment(true)
, lIndent(0)
, rIndent(0)
{
}
/**************************************************************************
* Add by: [email protected]
* Describe:
*
**************************************************************************/
CxRichEx::CxRichEx(CxRichEdit& re)
: xRichEdit(re)
{
}
CxRichEx::~CxRichEx()
{
Release();
}
Color cv[4]={Color(255,255,255,0),Color(255,185,185,185),Color(255,0,255,255),Color(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;
rcRelative.top = ps.y-xRect.top+sli.nPos;
//调整控件位置
rcRelative.top -= xRcFlate.top;
iHigh += xRcFlate.top+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;
rcRelative.top = ps.y-xRect.top+sli.nPos;
//调整控件位置
rcRelative.top -= xRcFlate.top;
iHigh += xRcFlate.top+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);
}