duilib中CTextUI控件设置文字时不刷新显示的bug

本文详细解析Duilib中CTextUI控件的自适应特性,探讨其实现原理,展示应用场景,并深入分析由此特性引发的问题及其解决方案。
  duilib中常用的文本控件CTextUI,有个很重要的特性:控件的长度会自适应所设置文本的长度,即所设置文本的的长度变化了,控件的长度也会随之变化。但是会有这样的问题:当设置的文本长度大于先前旧的文本的长度时,界面没有及时刷新,仍然显示的是之前的旧的文本,需要通过页面的切换才能刷新显示。下面我们来详细看一下自适应特性是如何实现的、是如何被应用的、自适应特性引起的问题以及问题是如何来解决的。

        1、自适应特性的实现

        CTextUI控件提供了EstimateSize接口,接口的代码如下所示。

  1. SIZE CTextUI::EstimateSize(SIZE szAvailable)  
  2. {  
  3.     RECT rcText = { 0, 0, MAX(szAvailable.cx, m_cxyFixed.cx), 9999 };  
  4.     rcText.left += m_rcTextPadding.left;  
  5.     rcText.right -= m_rcTextPadding.right;  
  6.     if( m_bShowHtml )  
  7.     {     
  8.         int nLinks = 0;  
  9.         CRenderEngine::DrawHtmlText(m_pManager->GetPaintDC(), m_pManager, rcText, m_sText, m_dwTextColor, NULL, NULL, nLinks, DT_CALCRECT | m_uTextStyle);  
  10.     }  
  11.     else  
  12.     {  
  13.         CRenderEngine::DrawText(m_pManager->GetPaintDC(), m_pManager, rcText, m_sText, m_dwTextColor, m_iFont, DT_CALCRECT | m_uTextStyle);  
  14.     }  
  15.     SIZE cXY = {rcText.right - rcText.left + m_rcTextPadding.left + m_rcTextPadding.right,  
  16.         rcText.bottom - rcText.top + m_rcTextPadding.top + m_rcTextPadding.bottom};  
  17.   
  18.     if( m_cxyFixed.cy != 0 ) cXY.cy = m_cxyFixed.cy;  
  19.     return cXY;  
  20. }  
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的代码。

  1. void CRenderEngine::DrawText( HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, DWORD dwTextColor, int iFont, UINT uStyle )  
  2. {  
  3.     ASSERT(::GetObjectType(hDC) == OBJ_DC || ::GetObjectType(hDC) == OBJ_MEMDC);  
  4.     if (pstrText == NULL || pManager == NULL) return;  
  5.     ::SetBkMode(hDC, TRANSPARENT);  
  6.     ::SetTextColor(hDC, RGB(GetBValue(dwTextColor), GetGValue(dwTextColor), GetRValue(dwTextColor)));  
  7.     HFONT hOldFont = (HFONT)::SelectObject(hDC, pManager->GetFont(iFont));  
  8.     ::DrawText(hDC, pstrText, -1, &rc, uStyle | DT_NOPREFIX );  
  9.     ::SelectObject(hDC, hOldFont);  
  10. }  
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的函数说明,如下所示。

  1. int DrawText(  
  2.   HDC hDC,          // handle to DC   
  3.   LPCTSTR lpString, // text to draw   
  4.   int nCount,       // text length   
  5.   LPRECT lpRect,    // formatting dimensions   
  6.   UINT uFormat      // text-drawing options   
  7. );  
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接口,接口的代码如下所示。

  1. void CControlUI::SetText( LPCTSTR pstrText )  
  2. {  
  3.     if( m_sText == pstrText )   
  4.     {  
  5.         return;  
  6.     }  
  7.   
  8.     m_sText = pstrText;  
  9.     Invalidate();  
  10. }  
void CControlUI::SetText( LPCTSTR pstrText )
{
    if( m_sText == pstrText ) 
	{
		return;
	}

    m_sText = pstrText;
    Invalidate();
}

        在上面的代码中调用CControlUI::Invalidate函数,

  1. void CControlUI::Invalidate()  
  2. {  
  3.     if( !IsVisible() ) return;  
  4.   
  5.     RECT invalidateRc = m_rcItem;  
  6.   
  7.     CControlUI* pParent = this;  
  8.     RECT rcTemp;  
  9.     RECT rcParent;  
  10.     while( pParent = pParent->GetParent() )  
  11.     {  
  12.         rcTemp = invalidateRc;  
  13.         rcParent = pParent->GetPos();  
  14.         if( !::IntersectRect(&invalidateRc, &rcTemp, &rcParent) )   
  15.         {  
  16.             return;  
  17.         }  
  18.     }  
  19.   
  20.     if( m_pManager != NULL ) m_pManager->Invalidate(invalidateRc);  
  21. }  
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中,即

  1. void CPaintManagerUI::Invalidate(RECT& rcItem)  
  2. {  
  3.     ::InvalidateRect(m_hWndPaint, &rcItem, FALSE);  
  4. }  
void CPaintManagerUI::Invalidate(RECT& rcItem)
{
    ::InvalidateRect(m_hWndPaint, &rcItem, FALSE);
}
        也就是最终调用API函数::InvalidateRect,产生一个WM_PAINT消息。于是进入CPaintManagerUI::MessageHandler中,查看收到WM_PAINT消息是如何处理的。

  1. bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)  
  2. {  
  3. ......  
  4.     case WM_PAINT:  
  5.         {  
  6.             // Should we paint?   
  7.             RECT rcPaint = { 0 };  
  8.             if( !::GetUpdateRect( m_hWndPaint, &rcPaint, FALSE) )   
  9.             {  
  10.                 return true;  
  11.             }  
  12.   
  13.             if( NULL == m_pRoot )   
  14.             {  
  15.                 PAINTSTRUCT ps = { 0 };  
  16.                 ::BeginPaint( m_hWndPaint, &ps );  
  17.                 ::EndPaint( m_hWndPaint, &ps );  
  18.                 return true;  
  19.             }              
  20.             // Do we need to resize anything?   
  21.             // This is the time where we layout the controls on the form.   
  22.             // We delay this even from the WM_SIZE messages since resizing can be   
  23.             // a very expensive operation.   
  24.             if( m_bUpdateNeeded )   
  25.             {  
  26.                 m_bUpdateNeeded = false;  
  27.                 RECT rcClient = { 0 };  
  28.                 ::GetClientRect( m_hWndPaint, &rcClient );  
  29.                 if( !::IsRectEmpty( &rcClient ) )   
  30.                 {  
  31.                     if( m_pRoot->IsUpdateNeeded() )   
  32.                     {  
  33.                         m_pRoot->SetPos( rcClient );  
  34.                         if( m_hDcOffscreen != NULL ) ::DeleteDC( m_hDcOffscreen );  
  35.                         if( m_hDcBackground != NULL ) ::DeleteDC( m_hDcBackground );  
  36.                         if( m_hbmpOffscreen != NULL ) ::DeleteObject( m_hbmpOffscreen );  
  37.                         if( m_hbmpBackground != NULL ) ::DeleteObject( m_hbmpBackground );  
  38.                         m_hDcOffscreen = NULL;  
  39.                         m_hDcBackground = NULL;  
  40.                         m_hbmpOffscreen = NULL;  
  41.                         m_hbmpBackground = NULL;  
  42.                     }  
  43.                     else   
  44.                     {  
  45.                         CControlUI* pControl = NULL;  
  46.                         while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )   
  47.                         {  
  48.                             pControl->SetPos( pControl->GetPos() );  
  49.                         }  
  50.                     }  
  51. ......  
  52. }  
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中代码,

  1. bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)  
  2. {  
  3. ......  
  4.     case WM_PAINT:  
  5.         {  
  6.  ......  
  7.             if( m_bUpdateNeeded )   
  8.             {  
  9.                 m_bUpdateNeeded = false;  
  10.                 RECT rcClient = { 0 };  
  11.                 ::GetClientRect( m_hWndPaint, &rcClient );  
  12.                 if( !::IsRectEmpty( &rcClient ) )   
  13.                 {  
  14.                     if( m_pRoot->IsUpdateNeeded() )   
  15.                     {  
  16.                         m_pRoot->SetPos( rcClient );  
  17.                         if( m_hDcOffscreen != NULL ) ::DeleteDC( m_hDcOffscreen );  
  18.                         if( m_hDcBackground != NULL ) ::DeleteDC( m_hDcBackground );  
  19.                         if( m_hbmpOffscreen != NULL ) ::DeleteObject( m_hbmpOffscreen );  
  20.                         if( m_hbmpBackground != NULL ) ::DeleteObject( m_hbmpBackground );  
  21.                         m_hDcOffscreen = NULL;  
  22.                         m_hDcBackground = NULL;  
  23.                         m_hbmpOffscreen = NULL;  
  24.                         m_hbmpBackground = NULL;  
  25.                     }  
  26.                     else   
  27.                     {  
  28.                         CControlUI* pControl = NULL;  
  29.                         while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )   
  30.                         {  
  31.                             pControl->SetPos( pControl->GetPos() );  
  32.                         }  
  33.                     }  
  34. ......  
  35. }  
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子控件的位置和尺寸,就让窗口的整个布局都重新刷新一遍,这肯定不可取的、不合理的。于是看其中的这段代码,
  1. else   
  2.   
  3.     CControlUI* pControl = NULL;  
  4. while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )   
  5. {  
  6.     pControl->SetPos( pControl->GetPos() );  
  7. }  
 					else 
					{
 						CControlUI* pControl = NULL;
						while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) 
						{
							pControl->SetPos( pControl->GetPos() );
						}
					}
继续跟到__FindControlFromUpdate函数,如下所示。

  1. CControlUI* CALLBACK CPaintManagerUI::__FindControlFromUpdate( CControlUI* pThis, LPVOID pData )  
  2. {  
  3.     return pThis->IsUpdateNeeded() ? pThis : NULL;  
  4. }  
CControlUI* CALLBACK CPaintManagerUI::__FindControlFromUpdate( CControlUI* pThis, LPVOID pData )
{
    return pThis->IsUpdateNeeded() ? pThis : NULL;
}
所以,我们只要将CTextUI所依附的父布局CVerticalLayoutUI或者CHorizontalLayoutUI等设置为需要刷新即可调用到CVerticalLayoutUI或者CHorizontalLayoutUI等的SetPos函数。所以最终的解决办法:在CTextUI中重写父类CControlUI中的虚函数SetText,添加的代码如下所示。

  1. void CTextUI::SetText( LPCTSTR pstrText )  
  2. {  
  3.     CControlUI::SetText( pstrText );  
  4.   
  5.     NeedParentUpdate();  
  6. }  
void CTextUI::SetText( LPCTSTR pstrText )
{
	CControlUI::SetText( pstrText );

	NeedParentUpdate();
}
为了说明问题,将CControlUI::NeedParentUpdate的代码也贴出来,
  1. void CControlUI::NeedParentUpdate()  
  2. {  
  3.     if( GetParent() )  
  4.     {  
  5.         GetParent()->NeedUpdate();  
  6.         GetParent()->Invalidate();  
  7.     }  
  8.     else  
  9.     {  
  10.         NeedUpdate();  
  11.     }  
  12.   
  13.     if( m_pManager != NULL )  
  14.     {  
  15.         m_pManager->NeedUpdate();  
  16.     }  
  17. }  
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消息处理分支的如下代码段,

  1. else   
  2.   
  3.     CControlUI* pControl = NULL;  
  4. while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) )   
  5. {  
  6.     pControl->SetPos( pControl->GetPos() );  
  7. }  
 					else 
					{
 						CControlUI* pControl = NULL;
						while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) 
						{
							pControl->SetPos( pControl->GetPos() );
						}
					}

最终解决本案例中CTextUI控件不刷新显示的问题。

 

http://blog.youkuaiyun.com/chenlycly/article/details/17291765#

1.duilib简介 duilib是一个开源的DirectUI界面库,简洁但是功能强大。而且还是BSD的license,所以即便是在商业上,大家也可以安心使用。 现在大家可以从这个网站获取到他们所有的源码:/p/duilib/ 为了让我们能更简单的了解其机制,我们按照如下顺序一步一步的来对他进行观察: 工具库:用于支撑整个项目的基础 控件库:这是dui最关键的部分之一,相信也是大家最关注的部分之一,另外这里也来看看它是如何管理这些控件的 消息流转:有了控件库,我们需要将Windows窗口的原生消息流转给这些控件,另外在这里也来看看Focus,Capture等等的实现 资源组织和皮肤加载:有了上面所有的这些,我们再来看看它是如何自动创建皮肤的 简单使用:最后,来看看到底要如何使用它 以下是duilib工程带的一副总体设计图,在看代码之前看看这幅图,对看代码会很有帮助。 duilib: 2.工具库 由于duilib没有对外部的任何库进行依赖,所以在其内部实现了很多用于支撑项目的基础类,这些类分布在Util文件夹中: UI相关:CPoint/CSize/CDuiRect 简单容器:CStdPtrArray/CStdValArray/CStdString/CStdStringPtrMap 上面这些类看名字就基本能够理解其具体的含义了,当然除了基本的基础库,还有一些和窗口使用相关的工具的封装: 窗口工具:WindowImplBase,这个工具我们在这里详述,后面会再次提到。 3.控件控件库在duilib的实现中被分为了两块:Core和Control: Core中包含的是所有控件公用的部分,里面主要是一些基类和绘制的封装。 Control中包含的就是各个同的控件的行为了。 Core部分和控件相关的类图非常简单: duilib-core: 3.1.控件基类:CControlUI CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。 他基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用,等等等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示,等等等等。 另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平使用的候就还是要使用的...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值