duilib的所有控件均绘制在唯一的真实窗口之中,本篇就具体看下这个绘制的过程。所有的绘制过程均在WM_PAINT消息处理过程中完成。由窗口及消息篇可以看到,窗口消息处理最终流到了CPaintManagerUI::MessageHandler中。包括WM_PAINT在内消息均在此函数中处理,我们仅关注WM_PAINT消息
1 case WM_PAINT: 2 { 3 // Should we paint? 4 RECT rcPaint = { 0 }; 5 if( !::GetUpdateRect(m_hWndPaint, &rcPaint, FALSE) ) return true; 6 if( m_pRoot == NULL ) { 7 PAINTSTRUCT ps = { 0 }; 8 ::BeginPaint(m_hWndPaint, &ps); 9 ::EndPaint(m_hWndPaint, &ps); 10 return true; 11 } 12 // Do we need to resize anything? 13 // This is the time where we layout the controls on the form. 14 // We delay this even from the WM_SIZE messages since resizing can be 15 // a very expensize operation. 16 if( m_bUpdateNeeded ) { 17 m_bUpdateNeeded = false; 18 RECT rcClient = { 0 }; 19 ::GetClientRect(m_hWndPaint, &rcClient); 20 if( !::IsRectEmpty(&rcClient) ) { 21 if( m_pRoot->IsUpdateNeeded() ) { 22 m_pRoot->SetPos(rcClient); 23 if( m_hDcOffscreen != NULL ) ::DeleteDC(m_hDcOffscreen); 24 if( m_hDcBackground != NULL ) ::DeleteDC(m_hDcBackground); 25 if( m_hbmpOffscreen != NULL ) ::DeleteObject(m_hbmpOffscreen); 26 if( m_hbmpBackground != NULL ) ::DeleteObject(m_hbmpBackground); 27 m_hDcOffscreen = NULL; 28 m_hDcBackground = NULL; 29 m_hbmpOffscreen = NULL; 30 m_hbmpBackground = NULL; 31 } 32 else { 33 CControlUI* pControl = NULL; 34 while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) { 35 pControl->SetPos( pControl->GetPos() ); 36 } 37 } 38 // We'll want to notify the window when it is first initialized 39 // with the correct layout. The window form would take the time 40 // to submit swipes/animations. 41 if( m_bFirstLayout ) { 42 m_bFirstLayout = false; 43 SendNotify(m_pRoot, _T("windowinit"), 0, 0, false); 44 } 45 } 46 } 47 // Set focus to first control? 48 if( m_bFocusNeeded ) { 49 SetNextTabControl(); 50 } 51 // 52 // Render screen 53 // 54 // Prepare offscreen bitmap? 55 if( m_bOffscreenPaint && m_hbmpOffscreen == NULL ) 56 { 57 RECT rcClient = { 0 }; 58 ::GetClientRect(m_hWndPaint, &rcClient); 59 m_hDcOffscreen = ::CreateCompatibleDC(m_hDcPaint); 60 m_hbmpOffscreen = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); 61 ASSERT(m_hDcOffscreen); 62 ASSERT(m_hbmpOffscreen); 63 } 64 // Begin Windows paint 65 PAINTSTRUCT ps = { 0 }; 66 ::BeginPaint(m_hWndPaint, &ps); 67 if( m_bOffscreenPaint ) 68 { 69 HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(m_hDcOffscreen, m_hbmpOffscreen); 70 int iSaveDC = ::SaveDC(m_hDcOffscreen); 71 if( m_bAlphaBackground ) { 72 if( m_hbmpBackground == NULL ) { 73 RECT rcClient = { 0 }; 74 ::GetClientRect(m_hWndPaint, &rcClient); 75 m_hDcBackground = ::CreateCompatibleDC(m_hDcPaint);; 76 m_hbmpBackground = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); 77 ASSERT(m_hDcBackground); 78 ASSERT(m_hbmpBackground); 79 ::SelectObject(m_hDcBackground, m_hbmpBackground); 80 ::BitBlt(m_hDcBackground, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, 81 ps.rcPaint.bottom - ps.rcPaint.top, ps.hdc, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); 82 } 83 else 84 ::SelectObject(m_hDcBackground, m_hbmpBackground); 85 ::BitBlt(m_hDcOffscreen, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, 86 ps.rcPaint.bottom - ps.rcPaint.top, m_hDcBackground, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); 87 } 88 m_pRoot->DoPaint(m_hDcOffscreen, ps.rcPaint);//绘制控件 89 for( int i = 0; i < m_aPostPaintControls.GetSize(); i++ ) { 90 CControlUI* pPostPaintControl = static_cast<CControlUI*>(m_aPostPaintControls[i]); 91 pPostPaintControl->DoPostPaint(m_hDcOffscreen, ps.rcPaint); 92 } 93 ::RestoreDC(m_hDcOffscreen, iSaveDC); 94 ::BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, 95 ps.rcPaint.bottom - ps.rcPaint.top, m_hDcOffscreen, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); 96 ::SelectObject(m_hDcOffscreen, hOldBitmap); 97 98 if( m_bShowUpdateRect ) { 99 HPEN hOldPen = (HPEN)::SelectObject(ps.hdc, m_hUpdateRectPen); 100 ::SelectObject(ps.hdc, ::GetStockObject(HOLLOW_BRUSH)); 101 ::Rectangle(ps.hdc, rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom); 102 ::SelectObject(ps.hdc, hOldPen); 103 } 104 } 105 else 106 { 107 // A standard paint job 108 int iSaveDC = ::SaveDC(ps.hdc); 109 m_pRoot->DoPaint(ps.hdc, ps.rcPaint);//绘制控件 110 ::RestoreDC(ps.hdc, iSaveDC); 111 } 112 // All Done! 113 ::EndPaint(m_hWndPaint, &ps); 114 } 115 // If any of the painting requested a resize again, we'll need 116 // to invalidate the entire window once more. 117 if( m_bUpdateNeeded ) { 118 ::InvalidateRect(m_hWndPaint, NULL, FALSE); 119 } 120 return true;
在::BeginPaint(m_hWndPaint, &ps)和::EndPaint(m_hWndPaint, &ps)中间是窗口绘制部分,duilib包含了两种方式:双缓存方式(解决闪烁问题)和标准方式,默认为双缓存方式。两种方式最终都调用了m_pRoot->DoPaint,m_pRoot为控件容器,且DoPaint为虚函数,实际调用了CContainerUI::DoPaint
1 void CContainerUI::DoPaint(HDC hDC, const RECT& rcPaint) 2 { 3 RECT rcTemp = { 0 }; 4 if( !::IntersectRect(&rcTemp, &rcPaint, &m_rcItem) ) return; 5 6 CRenderClip clip; 7 CRenderClip::GenerateClip(hDC, rcTemp, clip); 8 CControlUI::DoPaint(hDC, rcPaint); 9 10 if( m_items.GetSize() > 0 ) { 11 RECT rc = m_rcItem; 12 rc.left += m_rcInset.left; 13 rc.top += m_rcInset.top; 14 rc.right -= m_rcInset.right; 15 rc.bottom -= m_rcInset.bottom; 16 if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() ) rc.right -= m_pVerticalScrollBar->GetFixedWidth(); 17 if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight(); 18 19 if( !::IntersectRect(&rcTemp, &rcPaint, &rc) ) { 20 for( int it = 0; it < m_items.GetSize(); it++ ) { 21 CControlUI* pControl = static_cast<CControlUI*>(m_items[it]); 22 if( !pControl->IsVisible() ) continue; 23 if( !::IntersectRect(&rcTemp, &rcPaint, &pControl->GetPos()) ) continue; 24 if( pControl ->IsFloat() ) { 25 if( !::IntersectRect(&rcTemp, &m_rcItem, &pControl->GetPos()) ) continue; 26 pControl->DoPaint(hDC, rcPaint); 27 } 28 } 29 } 30 else { 31 CRenderClip childClip; 32 CRenderClip::GenerateClip(hDC, rcTemp, childClip); 33 for( int it = 0; it < m_items.GetSize(); it++ ) { 34 CControlUI* pControl = static_cast<CControlUI*>(m_items[it]); 35 if( !pControl->IsVisible() ) continue; 36 if( !::IntersectRect(&rcTemp, &rcPaint, &pControl->GetPos()) ) continue; 37 if( pControl ->IsFloat() ) { 38 if( !::IntersectRect(&rcTemp, &m_rcItem, &pControl->GetPos()) ) continue; 39 CRenderClip::UseOldClipBegin(hDC, childClip); 40 pControl->DoPaint(hDC, rcPaint); 41 CRenderClip::UseOldClipEnd(hDC, childClip); 42 } 43 else { 44 if( !::IntersectRect(&rcTemp, &rc, &pControl->GetPos()) ) continue; 45 pControl->DoPaint(hDC, rcPaint); 46 } 47 } 48 } 49 } 50 51 if( m_pVerticalScrollBar != NULL && m_pVerticalScrollBar->IsVisible() ) { 52 if( ::IntersectRect(&rcTemp, &rcPaint, &m_pVerticalScrollBar->GetPos()) ) { 53 m_pVerticalScrollBar->DoPaint(hDC, rcPaint); 54 } 55 } 56 57 if( m_pHorizontalScrollBar != NULL && m_pHorizontalScrollBar->IsVisible() ) { 58 if( ::IntersectRect(&rcTemp, &rcPaint, &m_pHorizontalScrollBar->GetPos()) ) { 59 m_pHorizontalScrollBar->DoPaint(hDC, rcPaint); 60 } 61 } 62 }
控件容器绘制完自己后,遍历子控件(包括子控件容器)调用其DoPaint,完成子控件绘制
1 void CControlUI::DoPaint(HDC hDC, const RECT& rcPaint) 2 { 3 if( !::IntersectRect(&m_rcPaint, &rcPaint, &m_rcItem) ) return; 4 5 // 绘制循序:背景颜色->背景图->状态图->文本->边框 6 if( m_cxyBorderRound.cx > 0 || m_cxyBorderRound.cy > 0 ) { 7 CRenderClip roundClip; 8 CRenderClip::GenerateRoundClip(hDC, m_rcPaint, m_rcItem, m_cxyBorderRound.cx, m_cxyBorderRound.cy, roundClip); 9 PaintBkColor(hDC); 10 PaintBkImage(hDC); 11 PaintStatusImage(hDC); 12 PaintText(hDC); 13 PaintBorder(hDC); 14 } 15 else { 16 PaintBkColor(hDC); 17 PaintBkImage(hDC); 18 PaintStatusImage(hDC); 19 PaintText(hDC); 20 PaintBorder(hDC); 21 } 22 }
最终的绘制都是通过渲染引擎CRenderEngine实现的。
这样看来,整个绘制思路还是很清晰的:CPaintManagerUI::MessageHandler(WM_PAINT)--->CContainerUI::DoPaint--->CControlUI::DoPaint--->CRenderEngine。