文章目录
1.WM_PAINT消息
- 1.WM_PAINT消息
- 2.作者答疑
经过前面的解析,耐心的读者,对duilib这个库,不论从体系架构,还是从细节上,应该都有了一定程序的了解,本小节从界面库这个最核心的绘制流程上,给大家做轮廓上的解析,之所以这么说,因为一些细节只是为了支持更多的功能,但是不常用,所以留给感兴趣的读者自己研究。 首先用duilib创建的最上层窗口也是一个win32窗口,也就是我们继承至WindowImplBase基类创建的窗口,在窗口消息过程函数中响应窗口绘制消息WM_PAINT,从前面的源码分析可知,这个消息在WindowImplBase基类成员CPaintManagerUI对象中的消息函数MessageHandler中响应,由于整个消息函数源代码太长,就截取WM_PAINT消息部分,如下所示:
case WM_PAINT:
{
if( m_pRoot == NULL ) {
PAINTSTRUCT ps = { 0 };
::BeginPaint(m_hWndPaint, &ps);
CRenderEngine::DrawColor(m_hDcPaint, ps.rcPaint, 0xFF000000);
::EndPaint(m_hWndPaint, &ps);
return true;
}
RECT rcClient = { 0 };
::GetClientRect(m_hWndPaint, &rcClient);
RECT rcPaint = { 0 };
if( !::GetUpdateRect(m_hWndPaint, &rcPaint, FALSE) ) return true;
// Set focus to first control?
if( m_bFocusNeeded ) {
SetNextTabControl();
}
SetPainting(true);
bool bNeedSizeMsg = false;
DWORD dwWidth = rcClient.right - rcClient.left;
DWORD dwHeight = rcClient.bottom - rcClient.top;
SetPainting(true);
//是否需要刷新
if( m_bUpdateNeeded ) {
m_bUpdateNeeded = false;
if( !::IsRectEmpty(&rcClient) && !::IsIconic(m_hWndPaint) ) {
if( m_pRoot->IsUpdateNeeded() ) {
RECT rcRoot = 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;
if( m_bLayered ) {
rcRoot.left += m_rcLayeredInset.left;
rcRoot.top += m_rcLayeredInset.top;
rcRoot.right -= m_rcLayeredInset.right;
rcRoot.bottom -= m_rcLayeredInset.bottom;
}
m_pRoot->SetPos(rcRoot, true);
bNeedSizeMsg = true;
}
else {
CControlUI* pControl = NULL;
m_aFoundControls.Empty();
m_pRoot->FindControl(__FindControlsFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST | UIFIND_UPDATETEST);
for( int it = 0; it IsFloat() ) pControl->SetPos(pControl->GetPos(), true);
else pControl->SetPos(pControl->GetRelativePos(), true);
}
bNeedSizeMsg = true;
}
// 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);
if( m_bLayered && m_bLayeredChanged ) {
Invalidate();
SetPainting(false);
return true;
}
// 更新阴影窗口显示
m_shadow.Update(m_hWndPaint);
}
}
}
else if( m_bLayered && m_bLayeredChanged ) {
RECT rcRoot = rcClient;
if( m_pOffscreenBits ) ::ZeroMemory(m_pOffscreenBits, (rcRoot.right - rcRoot.left)
* (rcRoot.bottom - rcRoot.top) * 4);
rcRoot.left += m_rcLayeredInset.left;
rcRoot.top += m_rcLayeredInset.top;
rcRoot.right -= m_rcLayeredInset.right;
rcRoot.bottom -= m_rcLayeredInset.bottom;
m_pRoot->SetPos(rcRoot, true);
}
//与透明窗口相关
if( m_bLayered ) {
DWORD dwExStyle = ::GetWindowLong(m_hWndPaint, GWL_EXSTYLE);
DWORD dwNewExStyle = dwExStyle | WS_EX_LAYERED;
if(dwExStyle != dwNewExStyle) ::SetWindowLong(m_hWndPaint, GWL_EXSTYLE, dwNewExStyle);
m_bOffscreenPaint = true;
UnionRect(&rcPaint, &rcPaint, &m_rcLayeredUpdate);
if( rcPaint.right > rcClient.right ) rcPaint.right = rcClient.right;
if( rcPaint.bottom > rcClient.bottom ) rcPaint.bottom = rcClient.bottom;
::ZeroMemory(&m_rcLayeredUpdate, sizeof(m_rcLayeredUpdate));
}
//
// Render screen
//
// Prepare offscreen bitmap
if( m_bOffscreenPaint && m_hbmpOffscreen == NULL ) {
m_hDcOffscreen = ::CreateCompatibleDC(m_hDcPaint);
m_hbmpOffscreen = CRenderEngine::CreateARGB32Bitmap(m_hDcPaint, dwWidth, dwHeight, (LPBYTE*)&m_pOffscreenBits);
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_bLayered) {
for( LONG y = rcClient.bottom - rcPaint.bottom; y 8) * A / 255;
B = (BYTE)(*pOffscreenBits) * A / 255;
*pOffscreenBits = RGB(B, G, R) + ((DWORD)A 0 ) {
RECT rcInset = GetInset();
RECT rc = m_rcItem;
rc.left += rcInset.left;
rc.top += rcInset.top;
rc.right -= rcInset.right;
rc.bottom -= 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 IsVisible() ) continue;
if( !::IntersectRect(&rcTemp, &rcPaint, &pControl->GetPos()) ) continue;
if( pControl ->IsFloat() ) {
if( !::IntersectRect(&rcTemp, &m_rcItem, &pControl->GetPos()) ) continue;
if( !pControl->Paint(hDC, rcPaint, pStopControl) ) return false;//子控件绘制函数
}
}
}
else {
CRenderClip childClip;
CRenderClip::GenerateClip(hDC, rcTemp, childClip);
for( int it = 0; it IsVisible() ) continue;
if( !::IntersectRect(&rcTemp, &rcPaint, &pControl->GetPos()) ) continue;
if( pControl->IsFloat() ) {
if( !::IntersectRect(&rcTemp, &m_rcItem, &pControl->GetPos()) ) continue;
CRenderClip::UseOldClipBegin(hDC, childClip);
if( !pControl->Paint(hDC, rcPaint, pStopControl) ) return false;
CRenderClip::UseOldClipEnd(hDC, childClip);
}
else {
if( !::IntersectRect(&rcTemp, &rc, &pControl->GetPos()) ) continue;
if( !pControl->Paint(hDC, rcPaint, pStopControl) ) return false;
}
}
}
}
//垂直滚动条绘制
if( m_pVerticalScrollBar != NULL && m_pVerticalScrollBar->IsVisible() ) {
if( m_pVerticalScrollBar == pStopControl ) return false;
if( ::IntersectRect(&rcTemp, &rcPaint, &m_pVerticalScrollBar->GetPos()) ) {
if( !m_pVerticalScrollBar->Paint(hDC, rcPaint, pStopControl) ) return false;
}
}
//水平滚动条绘制
if( m_pHorizontalScrollBar != NULL && m_pHorizontalScrollBar->IsVisible() ) {
if( m_pHorizontalScrollBar == pStopControl ) return false;
if( ::IntersectRect(&rcTemp, &rcPaint, &m_pHorizontalScrollBar->GetPos()) ) {
if( !m_pHorizontalScrollBar->Paint(hDC, rcPaint, pStopControl) ) return false;
}
}
return true;
}
从上面的代码可以清晰看出其轮廓,绘制流程是个迭代算法,先调用Paint函数,如果是个容器,调用容器DoPaint函数,由容器DoPaint函数再调用父类CControlUI的DoPaint函数先绘制,然后再调用容器包含的子控件的Paint函数。如果是个简单子控件,则直接调用DoPaint函数函数完成绘制。直到所有控件绘制完毕。所有根控件CControlUI的绘制函数,这是个控件绘制最基本的流程,如下所示:
bool CControlUI::DoPaint(HDC hDC, const RECT& rcPaint, CControlUI* pStopControl)
{
// 绘制循序:背景颜色->背景图->状态图->文本->边框
SIZE cxyBorderRound;
RECT rcBorderSize;
if (m_pManager) {
cxyBorderRound = GetManager()->GetDPIObj()->Scale(m_cxyBorderRound);
rcBorderSize = GetManager()->GetDPIObj()->Scale(m_rcBorderSize);
}
else {
cxyBorderRound = m_cxyBorderRound;
rcBorderSize = m_rcBorderSize;
}
if( cxyBorderRound.cx > 0 || cxyBorderRound.cy > 0 ) {
CRenderClip roundClip;
CRenderClip::GenerateRoundClip(hDC, m_rcPaint, m_rcItem, cxyBorderRound.cx, cxyBorderRound.cy, roundClip);
PaintBkColor(hDC);
PaintBkImage(hDC);
PaintStatusImage(hDC);
PaintForeColor(hDC);
PaintForeImage(hDC);
PaintText(hDC);
PaintBorder(hDC);
}
else {
PaintBkColor(hDC);
PaintBkImage(hDC);
PaintStatusImage(hDC);
PaintForeColor(hDC);
PaintForeImage(hDC);
PaintText(hDC);
PaintBorder(hDC);
}
return true;
}
2.作者答疑
如有疑问,请留言。