duilib中CTextUI控件设置文字时不刷新显示的bug
来源:互联网 发布:java se 编辑:程序博客网 时间:2024/04/30 17:44
1、自适应特性的实现
CTextUI控件提供了EstimateSize接口,接口的代码如下所示。
- SIZE CTextUI::EstimateSize(SIZE szAvailable)
- {
- RECT rcText = { 0, 0, MAX(szAvailable.cx, m_cxyFixed.cx), 9999 };
- rcText.left += m_rcTextPadding.left;
- rcText.right -= m_rcTextPadding.right;
- if( m_bShowHtml )
- {
- int nLinks = 0;
- CRenderEngine::DrawHtmlText(m_pManager->GetPaintDC(), m_pManager, rcText, m_sText, m_dwTextColor, NULL, NULL, nLinks, DT_CALCRECT | m_uTextStyle);
- }
- else
- {
- CRenderEngine::DrawText(m_pManager->GetPaintDC(), m_pManager, rcText, m_sText, m_dwTextColor, m_iFont, DT_CALCRECT | m_uTextStyle);
- }
- SIZE cXY = {rcText.right - rcText.left + m_rcTextPadding.left + m_rcTextPadding.right,
- rcText.bottom - rcText.top + m_rcTextPadding.top + m_rcTextPadding.bottom};
- if( m_cxyFixed.cy != 0 ) cXY.cy = m_cxyFixed.cy;
- return cXY;
- }
SIZE CTextUI::EstimateSize(SIZE szAvailable){ RECT rcText = { 0, 0, MAX(szAvailable.cx, m_cxyFixed.cx), 9999 }; rcText.left += m_rcTextPadding.left; rcText.right -= m_rcTextPadding.right; if( m_bShowHtml ){ int nLinks = 0; CRenderEngine::DrawHtmlText(m_pManager->GetPaintDC(), m_pManager, rcText, m_sText, m_dwTextColor, NULL, NULL, nLinks, DT_CALCRECT | m_uTextStyle); } else{ CRenderEngine::DrawText(m_pManager->GetPaintDC(), m_pManager, rcText, m_sText, m_dwTextColor, m_iFont, DT_CALCRECT | m_uTextStyle); } SIZE cXY = {rcText.right - rcText.left + m_rcTextPadding.left + m_rcTextPadding.right, rcText.bottom - rcText.top + m_rcTextPadding.top + m_rcTextPadding.bottom}; if( m_cxyFixed.cy != 0 ) cXY.cy = m_cxyFixed.cy; return cXY;}从上面代码我们看出,给DrawHtmlText和DrawText函数均传入了rcText,并且在接下来的代码中,利用rcText修正了控件相关的位置信息。代码看起来会有疑问,本接口仅仅是用来估计控件的大小,为什么要调用DrawHtmlText和DrawText函数呢?是用这两个函数来实现估计的?那我们接着看看CRenderEngine::DrawText的代码。
- void CRenderEngine::DrawText( HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, DWORD dwTextColor, int iFont, UINT uStyle )
- {
- ASSERT(::GetObjectType(hDC) == OBJ_DC || ::GetObjectType(hDC) == OBJ_MEMDC);
- if (pstrText == NULL || pManager == NULL) return;
- ::SetBkMode(hDC, TRANSPARENT);
- ::SetTextColor(hDC, RGB(GetBValue(dwTextColor), GetGValue(dwTextColor), GetRValue(dwTextColor)));
- HFONT hOldFont = (HFONT)::SelectObject(hDC, pManager->GetFont(iFont));
- ::DrawText(hDC, pstrText, -1, &rc, uStyle | DT_NOPREFIX );
- ::SelectObject(hDC, hOldFont);
- }
void CRenderEngine::DrawText( HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, DWORD dwTextColor, int iFont, UINT uStyle ){ ASSERT(::GetObjectType(hDC) == OBJ_DC || ::GetObjectType(hDC) == OBJ_MEMDC); if (pstrText == NULL || pManager == NULL) return; ::SetBkMode(hDC, TRANSPARENT); ::SetTextColor(hDC, RGB(GetBValue(dwTextColor), GetGValue(dwTextColor), GetRValue(dwTextColor))); HFONT hOldFont = (HFONT)::SelectObject(hDC, pManager->GetFont(iFont)); ::DrawText(hDC, pstrText, -1, &rc, uStyle | DT_NOPREFIX ); ::SelectObject(hDC, hOldFont);}代码是将文字绘制到制定的dc上,那到底是如何实现估计的呢?只能一步步看,于是到msdn中查看::DrawText API的函数说明,如下所示。
- int DrawText(
- HDC hDC, // handle to DC
- LPCTSTR lpString, // text to draw
- int nCount, // text length
- LPRECT lpRect, // formatting dimensions
- UINT uFormat // text-drawing options
- );
int DrawText( HDC hDC, // handle to DC LPCTSTR lpString, // text to draw int nCount, // text length LPRECT lpRect, // formatting dimensions UINT uFormat // text-drawing options);注意第四个参数,对应的说明:
- lpRect
- [in/out] Pointer to a RECT structure that contains the rectangle (in logical coordinates) in which the text is to be formatted.
它既是一个传入参数,也是一个传出参数,传出应该就是所绘画文字绘画后所占用的实际区域大小。所以结合上面的追踪过程,正是给DrawHtmlText和DrawText函数均传入了rcText参数,并且执行完后将绘画的文字占用的实际大小返回给了rcText传了出来,进而用实际占用大小来重新更新CTextUI控件的大小,这样就实现了控件对文字长度的自适应特性。
2、自适应特性的使用
这种自适应的特性可以巧妙的用在一些场合下面。比如类似于QQ讨论组的聊天框,在聊天框的右侧会显示当前的讨论组成员列表,如果成员是管理员,我们需要在成员名称后面添加一个小图标,以标识该成员是管理员。一般的做法是,利用CContainer的自动布局特性,我们将不变宽度的控件设置固定宽度值,位于右边的管理员图标宽度也设置为固定值,这样中间的成员名称是文本,可长可短,不设置成固定值,使用CLabelUI控件即可。这样,中间的名称宽度就是可变的,但是这样做有个问题,管理员图标会固定在每个成员item的左右边,不能跟随名称紧贴名称后面,显得不太美观。后来考虑到CTextUI的对文字长度能自适应,就改用CTextUI,能很好的实现管理员图标能紧贴在成员名称后面。
3、自适应特性引起的问题及问题分析
上面列举的例子中,当成员名称过长时,由于讨论组成员列表的宽度是固定的,当名称过长时,会将管理员图标给挤掉,看不见了或显示不全。这该如何解决呢?好在CTextUI有个maxwidth属性来设置最大宽度,这样我们将管理员图标位置预留出来,估计出控件的最大宽度,设置一下即可,当成员名称显示不下时,以省略号显示。
除了这个问题,还有个更严重的问题:当设置的文本长度大于先前旧的文本的长度时,界面没有及时刷新,仍然显示的是之前的旧的文本,需要通过页面的切换才能刷新显示。CTextUI是duilib中的基本控件,有这个问题是没法使用的,是要想办法解决掉的。
原因估计是这样的:由于CTextUI是自适应文字宽度的,对于旧的能显示全的文字,宽度是和文字宽度一致的;当我们设置更长的文字时,估计没有没有触发CTextUI::EstimateSize接口的调用,导致控件还是以前的大小,从而出现新文字没有刷新或显示不全的问题。
下面我们就来从代码中看一下不能刷新显示的问题是如何产生的。设置文字时,会调用基类CControlUI的SetText接口,接口的代码如下所示。
- void CControlUI::SetText( LPCTSTR pstrText )
- {
- if( m_sText == pstrText )
- {
- return;
- }
- m_sText = pstrText;
- Invalidate();
- }
void CControlUI::SetText( LPCTSTR pstrText ){ if( m_sText == pstrText ) {return;} m_sText = pstrText; Invalidate();}
在上面的代码中调用CControlUI::Invalidate函数,
- void CControlUI::Invalidate()
- {
- if( !IsVisible() ) return;
- RECT invalidateRc = m_rcItem;
- CControlUI* pParent = this;
- RECT rcTemp;
- RECT rcParent;
- while( pParent = pParent->GetParent() )
- {
- rcTemp = invalidateRc;
- rcParent = pParent->GetPos();
- if( !::IntersectRect(&invalidateRc, &rcTemp, &rcParent) )
- {
- return;
- }
- }
- if( m_pManager != NULL ) m_pManager->Invalidate(invalidateRc);
- }
void CControlUI::Invalidate(){if( !IsVisible() ) return;RECT invalidateRc = m_rcItem;CControlUI* pParent = this;RECT rcTemp;RECT rcParent;while( pParent = pParent->GetParent() ){rcTemp = invalidateRc;rcParent = pParent->GetPos();if( !::IntersectRect(&invalidateRc, &rcTemp, &rcParent) ) {return;}}if( m_pManager != NULL ) m_pManager->Invalidate(invalidateRc);}然后调到CPaintManagerUI::Invalidate中,即
- void CPaintManagerUI::Invalidate(RECT& rcItem)
- {
- ::InvalidateRect(m_hWndPaint, &rcItem, FALSE);
- }
void CPaintManagerUI::Invalidate(RECT& rcItem){ ::InvalidateRect(m_hWndPaint, &rcItem, FALSE);}也就是最终调用API函数::InvalidateRect,产生一个WM_PAINT消息。于是进入CPaintManagerUI::MessageHandler中,查看收到WM_PAINT消息是如何处理的。
- bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
- {
- ......
- case WM_PAINT:
- {
- // Should we paint?
- RECT rcPaint = { 0 };
- if( !::GetUpdateRect( m_hWndPaint, &rcPaint, FALSE) )
- {
- return true;
- }
- if( NULL == m_pRoot )
- {
- PAINTSTRUCT ps = { 0 };
- ::BeginPaint( m_hWndPaint, &ps );
- ::EndPaint( m_hWndPaint, &ps );
- return true;
- }
- // Do we need to resize anything?
- // This is the time where we layout the controls on the form.
- // We delay this even from the WM_SIZE messages since resizing can be
- // a very expensive operation.
- if( m_bUpdateNeeded )
- {
- m_bUpdateNeeded = false;
- RECT rcClient = { 0 };
- ::GetClientRect( m_hWndPaint, &rcClient );
- if( !::IsRectEmpty( &rcClient ) )
- {
- if( m_pRoot->IsUpdateNeeded() )
- {
- m_pRoot->SetPos( rcClient );
- if( m_hDcOffscreen != NULL ) ::DeleteDC( m_hDcOffscreen );
- if( m_hDcBackground != NULL ) ::DeleteDC( m_hDcBackground );
- if( m_hbmpOffscreen != NULL ) ::DeleteObject( m_hbmpOffscreen );
- if( m_hbmpBackground != NULL ) ::DeleteObject( m_hbmpBackground );
- m_hDcOffscreen = NULL;
- m_hDcBackground = NULL;
- m_hbmpOffscreen = NULL;
- m_hbmpBackground = NULL;
- }
- else
- {
- CControlUI* pControl = NULL;
- while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )
- {
- pControl->SetPos( pControl->GetPos() );
- }
- }
- ......
- }
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes){...... case WM_PAINT: { // Should we paint? RECT rcPaint = { 0 }; if( !::GetUpdateRect( m_hWndPaint, &rcPaint, FALSE) ) {return true;} if( NULL == m_pRoot ) { PAINTSTRUCT ps = { 0 }; ::BeginPaint( m_hWndPaint, &ps ); ::EndPaint( m_hWndPaint, &ps ); return true; } // Do we need to resize anything? // This is the time where we layout the controls on the form. // We delay this even from the WM_SIZE messages since resizing can be // a very expensive operation. if( m_bUpdateNeeded ) { m_bUpdateNeeded = false; RECT rcClient = { 0 }; ::GetClientRect( m_hWndPaint, &rcClient ); if( !::IsRectEmpty( &rcClient ) ) { if( m_pRoot->IsUpdateNeeded() ) { m_pRoot->SetPos( rcClient ); if( m_hDcOffscreen != NULL ) ::DeleteDC( m_hDcOffscreen ); if( m_hDcBackground != NULL ) ::DeleteDC( m_hDcBackground ); if( m_hbmpOffscreen != NULL ) ::DeleteObject( m_hbmpOffscreen ); if( m_hbmpBackground != NULL ) ::DeleteObject( m_hbmpBackground ); m_hDcOffscreen = NULL; m_hDcBackground = NULL; m_hbmpOffscreen = NULL; m_hbmpBackground = NULL; }else {CControlUI* pControl = NULL;while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) {pControl->SetPos( pControl->GetPos() );}}......}上面只给出了一部分代码。搜索一下调用EstimateSize函数的地方,发现CVerticalLayoutUI、CHorizontalLayoutUI、CTileLayoutUI和CTabLayoutUI类的SetPos函数中都会调到EstimateSize函数。从上面的代码可以看出,仅仅是产生一个WM_PAINT消息是不行的,是执行不到CVerticalLayoutUI、CHorizontalLayoutUI、CTileLayoutU等类的SetPos函数,执行不到CTextUI::EstimateSize函数的。也就是说,在设置文字的CControlUI::SetText函数中仅仅调用CControlUI::Invalidate函数是行不通的,是没法更新CTextUI控件大小的。
要调用到CVerticalLayoutUI、CHorizontalLayoutUI、CTileLayoutU等类的SetPos函数,首先要CPaintManagerUI::m_bUpdateNeeded为TRUE,那是不是要调用CPaintManagerUI::NeedUpdate(),先将m_bUpdateNeeded置为TRUE呢?于是加上代码,CTextUI文字还是刷新不了。于是继续看上面的代码,发现只有当CControlUI::m_bUpdateNeeded为TRUE时,才会调用控件对应的SetPos接口。于是有添加了CControlUI::NeedUpdate调用,问题还是没解决。其实,此时只是触发了控件本身CTextUI::SetPos的调用,此函数并没有调用CTextUI::EstimateSize,当然CTextUI::SetPos中不可能直接调用CTextUI::EstimateSize,是由于牵涉到父布局中的所有子控件(包括CTextUI控件)的摆放,理应由父布局统一统筹。
4、自适应特性引起的不刷新问题的解决
那如何才能调到CTextUI::EstimateSize呢?该如何处理呢?考虑到CVerticalLayoutUI、CHorizontalLayoutUI、CTileLayoutUI和CTabLayoutUI类的SetPos函数中都会调到EstimateSize函数,而CTextUI控件一般是依附在CVerticalLayoutUI、CHorizontalLayoutUI等父布局下的,那么只要我们触发这些父布局调用对应的SetPos,就可以调用到CTextUI::EstimateSize函数,进而利用最新的文字来估计CTextUI控件的尺寸,将CTextUI控件的尺寸更新掉,从而能让最新的文字刷新显示出来。
那如何触发这些父布局调用对应的SetPos呢?我们且看CPaintManagerUI::MessageHandler中代码,
- bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
- {
- ......
- case WM_PAINT:
- {
- ......
- if( m_bUpdateNeeded )
- {
- m_bUpdateNeeded = false;
- RECT rcClient = { 0 };
- ::GetClientRect( m_hWndPaint, &rcClient );
- if( !::IsRectEmpty( &rcClient ) )
- {
- if( m_pRoot->IsUpdateNeeded() )
- {
- m_pRoot->SetPos( rcClient );
- if( m_hDcOffscreen != NULL ) ::DeleteDC( m_hDcOffscreen );
- if( m_hDcBackground != NULL ) ::DeleteDC( m_hDcBackground );
- if( m_hbmpOffscreen != NULL ) ::DeleteObject( m_hbmpOffscreen );
- if( m_hbmpBackground != NULL ) ::DeleteObject( m_hbmpBackground );
- m_hDcOffscreen = NULL;
- m_hDcBackground = NULL;
- m_hbmpOffscreen = NULL;
- m_hbmpBackground = NULL;
- }
- else
- {
- CControlUI* pControl = NULL;
- while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )
- {
- pControl->SetPos( pControl->GetPos() );
- }
- }
- ......
- }
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes){...... case WM_PAINT: { ...... if( m_bUpdateNeeded ) { m_bUpdateNeeded = false; RECT rcClient = { 0 }; ::GetClientRect( m_hWndPaint, &rcClient ); if( !::IsRectEmpty( &rcClient ) ) { if( m_pRoot->IsUpdateNeeded() ) { m_pRoot->SetPos( rcClient ); if( m_hDcOffscreen != NULL ) ::DeleteDC( m_hDcOffscreen ); if( m_hDcBackground != NULL ) ::DeleteDC( m_hDcBackground ); if( m_hbmpOffscreen != NULL ) ::DeleteObject( m_hbmpOffscreen ); if( m_hbmpBackground != NULL ) ::DeleteObject( m_hbmpBackground ); m_hDcOffscreen = NULL; m_hDcBackground = NULL; m_hbmpOffscreen = NULL; m_hbmpBackground = NULL; } else { CControlUI* pControl = NULL;while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) {pControl->SetPos( pControl->GetPos() );}}......}如果让整个窗口的m_pRoot全布局全部刷新一遍,肯定实现上述更新CTextUI控件尺寸的目的。但是这代价太高了,仅仅是为了刷新一个CTextUI子控件的位置和尺寸,就让窗口的整个布局都重新刷新一遍,这肯定不可取的、不合理的。于是看其中的这段代码,
- else
- CControlUI* pControl = NULL;
- while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )
- {
- pControl->SetPos( pControl->GetPos() );
- }
else { CControlUI* pControl = NULL;while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) {pControl->SetPos( pControl->GetPos() );}}继续跟到__FindControlFromUpdate函数,如下所示。
- CControlUI* CALLBACK CPaintManagerUI::__FindControlFromUpdate( CControlUI* pThis, LPVOID pData )
- {
- return pThis->IsUpdateNeeded() ? pThis : NULL;
- }
CControlUI* CALLBACK CPaintManagerUI::__FindControlFromUpdate( CControlUI* pThis, LPVOID pData ){ return pThis->IsUpdateNeeded() ? pThis : NULL;}所以,我们只要将CTextUI所依附的父布局CVerticalLayoutUI或者CHorizontalLayoutUI等设置为需要刷新即可调用到CVerticalLayoutUI或者CHorizontalLayoutUI等的SetPos函数。所以最终的解决办法:在CTextUI中重写父类CControlUI中的虚函数SetText,添加的代码如下所示。
- void CTextUI::SetText( LPCTSTR pstrText )
- {
- CControlUI::SetText( pstrText );
- NeedParentUpdate();
- }
void CTextUI::SetText( LPCTSTR pstrText ){CControlUI::SetText( pstrText );NeedParentUpdate();}为了说明问题,将CControlUI::NeedParentUpdate的代码也贴出来,
- void CControlUI::NeedParentUpdate()
- {
- if( GetParent() )
- {
- GetParent()->NeedUpdate();
- GetParent()->Invalidate();
- }
- else
- {
- NeedUpdate();
- }
- if( m_pManager != NULL )
- {
- m_pManager->NeedUpdate();
- }
- }
void CControlUI::NeedParentUpdate(){ if( GetParent() ) { GetParent()->NeedUpdate(); GetParent()->Invalidate(); } else { NeedUpdate(); } if( m_pManager != NULL ) { m_pManager->NeedUpdate(); }}所以CTextUI::SetText不仅将父布局设置为需要刷新状态,而且调用CPaintManagerUI::NeedUpdate将m_bUpdateNeeded置为TRUE,进而保证代码能运行到CPaintManagerUI::MessageHandler中的WM_PAINT消息处理分支的如下代码段,
- else
- CControlUI* pControl = NULL;
- while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )
- {
- pControl->SetPos( pControl->GetPos() );
- }
else { CControlUI* pControl = NULL;while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) {pControl->SetPos( pControl->GetPos() );}}
最终解决本案例中CTextUI控件不刷新显示的问题。
http://blog.csdn.net/chenlycly/article/details/17291765#
- duilib中CTextUI控件设置文字时不刷新显示的bug
- duilib中CTextUI控件设置文字时不刷新显示的bug
- duilib List控件,横向滚动时列表项不移动或者显示错位的bug的修复
- [duilib]修复UIOption同时显示背景图和背景色时,背景图不显示的bug
- duilib 修复CTreeViewUI控件动态添加子控件时,对是否显示判断不足的bug
- duilib中设置不激活窗口后移动无法立即跟随的bug
- duilib的Combo控件滚动条不显示的问题
- duilib List控件,横向滚动时列表项不移动或者移动错位的bug的修复
- 【duilib界面库】duilib界面库(干货) 修复UIScrollBar鼠标移出控件外显示异常的BUG
- 关于Drawable设置到控件中不显示的问题
- duilib 修复 容器控件 rightbordersize和bottombordersize属性显示错误的bug
- duilib 修复Text控件无法设置宽度的bug,增加自动加算宽度的属性
- 修复duilib CEditUI控件和CWebBrowserUI控件中按Tab键无法切换焦点的bug
- DuiLib中CTileLayoutUI的一个bug
- Android控件TextView中ellipsize属性(设置当文字长度超过textview宽度时的显示方式)
- duilib CTextUI 纯字母不能换行
- Duilib 源码分析之 CTextUI 篇
- duilib 修复CTreeViewUI复选功能判断不准确的bug
- Android 如何修改factory mode下FM的默认测试频点及阀值
- 基于S3C2440的Linux-3.6.6移植——DM9000网卡驱动移植
- Tomcat启动内存设置
- 生成地址json地区树状数据
- LeetCode :: Remove Duplicates from Sorted Array [详细分析]
- duilib中CTextUI控件设置文字时不刷新显示的bug
- css实现文字竖排
- rac环境下vip/public/private IP的区别
- 统计语言模型学习笔记
- win7 vmware10 网络配置
- 并查集
- 使用SQL语句清空数据库所有表的数据
- ffmpeg 实时处理
- 抽象工厂模式