使用duilib创建的主窗口绘制工作全都发生在一个 真实存在的主窗口句柄当中,这个绘制过程稍稍有些复杂,但再复杂也逃不过WM_PAINT消息,所有的绘制工作也都由这个WM_PAINT消息完成.在上几篇中,我们总结了,所有的窗口消息都被CPaintManager类拦截处理,它成为所有消息的路由中心,我们看看WM_PAINT消息的处理:
case WM_PAINT: { // Should we paint? RECT rcPaint = { 0 }; if( !::GetUpdateRect(m_hWndPaint, &rcPaint, FALSE) ) return true; if( m_pRoot == NULL ) { 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 expensize 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() ); } } // We'll want to notify the window when it is first initialized // with the correct layout. The window form would take the time // to submit swipes/animations. if( m_bFirstLayout ) { m_bFirstLayout = false; SendNotify(m_pRoot, DUI_MSGTYPE_WINDOWINIT, 0, 0, false); } } } // Set focus to first control? if( m_bFocusNeeded ) { SetNextTabControl(); } // // Render screen // // Prepare offscreen bitmap? if( m_bOffscreenPaint && m_hbmpOffscreen == NULL ) { RECT rcClient = { 0 }; ::GetClientRect(m_hWndPaint, &rcClient); m_hDcOffscreen = ::CreateCompatibleDC(m_hDcPaint); m_hbmpOffscreen = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); ASSERT(m_hDcOffscreen); ASSERT(m_hbmpOffscreen); } // Begin Windows paint PAINTSTRUCT ps = { 0 }; ::BeginPaint(m_hWndPaint, &ps); if( m_bOffscreenPaint ) { HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(m_hDcOffscreen, m_hbmpOffscreen); int iSaveDC = ::SaveDC(m_hDcOffscreen); if( m_bAlphaBackground ) { if( m_hbmpBackground == NULL ) { RECT rcClient = { 0 }; ::GetClientRect(m_hWndPaint, &rcClient); m_hDcBackground = ::CreateCompatibleDC(m_hDcPaint);; m_hbmpBackground = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); ASSERT(m_hDcBackground); ASSERT(m_hbmpBackground); ::SelectObject(m_hDcBackground, m_hbmpBackground); ::BitBlt(m_hDcBackground, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, ps.hdc, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); } else ::SelectObject(m_hDcBackground, m_hbmpBackground); ::BitBlt(m_hDcOffscreen, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, m_hDcBackground, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); } //绘制控件 m_pRoot->DoPaint(m_hDcOffscreen, ps.rcPaint); for( int i = 0; i < m_aPostPaintControls.GetSize(); i++ ) { CControlUI* pPostPaintControl = static_cast<CControlUI*>(m_aPostPaintControls[i]); pPostPaintControl->DoPostPaint(m_hDcOffscreen, ps.rcPaint); } ::RestoreDC(m_hDcOffscreen, iSaveDC); ::BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, m_hDcOffscreen, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); ::SelectObject(m_hDcOffscreen, hOldBitmap); if( m_bShowUpdateRect ) { HPEN hOldPen = (HPEN)::SelectObject(ps.hdc, m_hUpdateRectPen); ::SelectObject(ps.hdc, ::GetStockObject(HOLLOW_BRUSH)); ::Rectangle(ps.hdc, rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom); ::SelectObject(ps.hdc, hOldPen); } } else { // A standard paint job int iSaveDC = ::SaveDC(ps.hdc); //绘制控件 m_pRoot->DoPaint(ps.hdc, ps.rcPaint); ::RestoreDC(ps.hdc, iSaveDC); } // All Done! ::EndPaint(m_hWndPaint, &ps); } // If any of the painting requested a resize again, we'll need // to invalidate the entire window once more. if( m_bUpdateNeeded ) { ::InvalidateRect(m_hWndPaint, NULL, FALSE); } return true;
所有的绘制工作都会回到基类CControlUI的DoPaint() 代码如下:
void CControlUI::DoPaint(HDC hDC, const RECT& rcPaint) { if( !::IntersectRect(&m_rcPaint, &rcPaint, &m_rcItem) ) return; // 绘制循序:背景颜色->背景图->状态图->文本->边框 if( m_cxyBorderRound.cx > 0 || m_cxyBorderRound.cy > 0 ) { CRenderClip roundClip; CRenderClip::GenerateRoundClip(hDC, m_rcPaint, m_rcItem, m_cxyBorderRound.cx, m_cxyBorderRound.cy, roundClip); PaintBkColor(hDC); PaintBkImage(hDC); PaintStatusImage(hDC); PaintText(hDC); PaintBorder(hDC); } else { PaintBkColor(hDC); PaintBkImage(hDC); PaintStatusImage(hDC); PaintText(hDC); PaintBorder(hDC); } }
但是,这里又引出一个问题,绘制工作是如何路由到CControlUI的DoPaint()中的呢,如果程序中比较简单,就像我们现在这样,没有用到容器,只有一个按钮,那么就相对简单,调用m_pRoot->DoPaint()直到调用的就是基类的DoPaint():
假如我们的窗口中,使用的控件不只一个,必然用到容器,这时候再调用m_pRoot->DoPaint()就不是直接调用CControlUI::DoPaint()而是调用的容器类的DoPaint(),来看代码:
void CContainerUI::DoPaint(HDC hDC, const RECT& rcPaint) { RECT rcTemp = { 0 }; if( !::IntersectRect(&rcTemp, &rcPaint, &m_rcItem) ) return; CRenderClip clip; CRenderClip::GenerateClip(hDC, rcTemp, clip); CControlUI::DoPaint(hDC, rcPaint); if( m_items.GetSize() > 0 ) { RECT rc = m_rcItem; rc.left += m_rcInset.left; rc.top += m_rcInset.top; rc.right -= m_rcInset.right; rc.bottom -= m_rcInset.bottom; if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() ) rc.right -= m_pVerticalScrollBar->GetFixedWidth(); if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight(); if( !::IntersectRect(&rcTemp, &rcPaint, &rc) ) { for( int it = 0; it < m_items.GetSize(); it++ ) { CControlUI* pControl = static_cast<CControlUI*>(m_items[it]); if( !pControl->IsVisible() ) continue; if( !::IntersectRect(&rcTemp, &rcPaint, &pControl->GetPos()) ) continue; if( pControl ->IsFloat() ) { if( !::IntersectRect(&rcTemp, &m_rcItem, &pControl->GetPos()) ) continue; pControl->DoPaint(hDC, rcPaint); } } } else { CRenderClip childClip; CRenderClip::GenerateClip(hDC, rcTemp, childClip); for( int it = 0; it < m_items.GetSize(); it++ ) { CControlUI* pControl = static_cast<CControlUI*>(m_items[it]); if( !pControl->IsVisible() ) continue; if( !::IntersectRect(&rcTemp, &rcPaint, &pControl->GetPos()) ) continue; if( pControl ->IsFloat() ) { if( !::IntersectRect(&rcTemp, &m_rcItem, &pControl->GetPos()) ) continue; CRenderClip::UseOldClipBegin(hDC, childClip); pControl->DoPaint(hDC, rcPaint); CRenderClip::UseOldClipEnd(hDC, childClip); } else { if( !::IntersectRect(&rcTemp, &rc, &pControl->GetPos()) ) continue; pControl->DoPaint(hDC, rcPaint); } } } } if( m_pVerticalScrollBar != NULL && m_pVerticalScrollBar->IsVisible() ) { if( ::IntersectRect(&rcTemp, &rcPaint, &m_pVerticalScrollBar->GetPos()) ) { m_pVerticalScrollBar->DoPaint(hDC, rcPaint); } } if( m_pHorizontalScrollBar != NULL && m_pHorizontalScrollBar->IsVisible() ) { if( ::IntersectRect(&rcTemp, &rcPaint, &m_pHorizontalScrollBar->GetPos()) ) { m_pHorizontalScrollBar->DoPaint(hDC, rcPaint); } } }
控件容器绘制完自己后,遍历子控件(包括子控件容器)调用其DoPaint,完成子控件绘制(代码已经在上上面贴出);
最终的绘制都是通过渲染引擎CRenderEngine实现的。
这样看来,整个绘制思路还是很清晰的:CPaintManagerUI::MessageHandler(WM_PAINT)--->CContainerUI::DoPaint--->CControlUI::DoPaint--->CRenderEngine。
OK绘制工作完成!