void CControlBar::EnableDocking(DWORD dwDockStyle) { ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY|CBRS_FLOAT_MULTI)) == 0); ASSERT(((dwDockStyle & CBRS_FLOAT_MULTI) == 0) || ((m_dwStyle & CBRS_SIZE_DYNAMIC) == 0)); m_dwDockStyle = dwDockStyle; if (m_pDockContext == NULL) m_pDockContext = new CDockContext(this); if (m_hWndOwner == NULL) m_hWndOwner = ::GetParent(m_hWnd); }
在创建CControlBar类的窗口之后,需要调用两次EnableDocking函数。一次调用相应的浮动窗口的EnableDocking函数设置浮动窗口的一些属性,然后调用父窗口的EnableDocking函数设置父窗口浮动的属性。首先验证窗口创建的属性,因为浮动窗口只可能存在两种情况,第一种依赖在父窗口的边缘,这个属性设置为CBRS_ALIGN_ANY表明依靠到父窗口的某一个边缘,这个边缘由创建时的CBRS_XXX(表示左右上下的四个方位);如果设置为CBRS_FLOAT_MULTI则在创建的时候不能设置CBRS_SIZE_DYNAMIC属性。如果没有设置CBRS_SIZE_DYNAMIC则不论是依靠还是浮动窗口的大小不会改变,不过在用鼠标将工具条浮动之后就不能依靠到父窗口了。验证参数之后,就会根据需要获取父窗口句柄以及浮动的上下文类。其中CDockContext包含浮动窗口的显示信息包括窗口的高和宽以及浮动属性父窗口等等。
void CFrameWnd::EnableDocking(DWORD dwDockStyle) { ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY|CBRS_FLOAT_MULTI)) == 0); m_pFloatingFrameClass = RUNTIME_CLASS(CMiniDockFrameWnd); for (int i = 0; i < 4; i++) { if (dwDockBarMap[i][1] & dwDockStyle & CBRS_ALIGN_ANY) { CDockBar* pDock = (CDockBar*)GetControlBar(dwDockBarMap[i][0]); if (pDock == NULL) { pDock = new CDockBar; if (!pDock->Create(this, WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_CHILD|WS_VISIBLE | dwDockBarMap[i][1], dwDockBarMap[i][0])) { AfxThrowResourceException(); } } } } }
函数开始部分同样验证CBRS属性,然后根据需要创建四个方向上的DockBar类。这些类实现真正的浮动操作(在这里有一个疑问创建的CDockBar怎么和父窗口的额链表关联起来的,这些CDockBar创建之后就可以用于浮动浮动条了)。其中dwDockBarMap是一个二维数组,每一行第一个数据表示创建的窗口的ID,第二个数据表示创建窗口的CBRS属性,总共四行数据表示四个方向上的DockBar。
void CFrameWnd::DockControlBar(CControlBar* pBar, CDockBar* pDockBar, LPCRECT lpRect) { if (pDockBar == NULL) { for (int i = 0; i < 4; i++) { if ((dwDockBarMap[i][1] & CBRS_ALIGN_ANY) == (pBar->m_dwStyle & CBRS_ALIGN_ANY)) { pDockBar = (CDockBar*)GetControlBar(dwDockBarMap[i][0]); break; } } } pDockBar->DockControlBar(pBar, lpRect); }
利用上面的函数为浮动窗口分配显示空间,不过在程序设计的时候调用的是这个函数的重载函数,传入的是CControlBar类,其余的参数适用默认参数。而MFC在实现的时候会将上面的参数转入到这个函数,所以实际实现是这个函数,不过从上面的实现来看这个函数也只是作为一个stub函数,再找到相应的CDockBar之后就调用CDockBar实现真正的浮动操作,而这个CDockBar正是之前创建而存放在链表中的。
void CDockBar::DockControlBar(CControlBar* pBar, LPCRECT lpRect) { CRect rectBar; pBar->GetWindowRect(&rectBar); if (pBar->m_pDockBar == this && (lpRect == NULL || rectBar == *lpRect)) { return; } if (m_bFloating && (pBar->m_dwDockStyle & CBRS_FLOAT_MULTI)) m_dwStyle |= CBRS_FLOAT_MULTI; m_dwStyle &= ~(CBRS_SIZE_FIXED | CBRS_SIZE_DYNAMIC); m_dwStyle |= pBar->m_dwStyle & (CBRS_SIZE_FIXED | CBRS_SIZE_DYNAMIC); if (!(m_dwStyle & CBRS_FLOAT_MULTI)) { TCHAR szTitle[_MAX_PATH]; pBar->GetWindowText(szTitle, _countof(szTitle)); AfxSetWindowText(m_hWnd, szTitle); } DWORD dwStyle = pBar->GetBarStyle(); dwStyle &= ~(CBRS_ALIGN_ANY); dwStyle |= (m_dwStyle & CBRS_ALIGN_ANY) | CBRS_BORDER_ANY; if (m_bFloating) dwStyle |= CBRS_FLOATING; else dwStyle &= ~CBRS_FLOATING; pBar->SetBarStyle(dwStyle); BOOL bShow = FALSE; if (pBar->m_pDockBar != this && pBar->IsWindowVisible()) { pBar->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_HIDEWINDOW); bShow = TRUE; } int nPos = -1; if (lpRect != NULL) { CRect rect(lpRect); ScreenToClient(&rect); CPoint ptMid(rect.left + rect.Width()/2, rect.top + rect.Height()/2); nPos = Insert(pBar, rect, ptMid); pBar->SetWindowPos(NULL, rect.left, rect.top, rect.Width(), rect.Height(), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS); } else { m_arrBars.Add(pBar); m_arrBars.Add(NULL); pBar->SetWindowPos(NULL, -afxData.cxBorder2, -afxData.cyBorder2, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS); } if (pBar->GetParent() != this) pBar->SetParent(this); if (pBar->m_pDockBar == this) pBar->m_pDockBar->RemoveControlBar(pBar, nPos); else if (pBar->m_pDockBar != NULL) pBar->m_pDockBar->RemoveControlBar(pBar, -1, m_bFloating && !pBar->m_pDockBar->m_bFloating); pBar->m_pDockBar = this; if (bShow) { pBar->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_SHOWWINDOW); } RemovePlaceHolder(pBar); CFrameWnd* pFrameWnd = GetDockingFrame(); pFrameWnd->DelayRecalcLayout(); }
首先验证函数的CControlBar是否已经传递进来进行了处理,当CControlBar的DockBar等于当前的DockBar同时参数为NULL或者表明当前窗口的矩形区域不需要更新的时候,表明不需要处理。接下来的处理看起来很复杂,其实在情理当中,实际上就根据传递进来的CControlBar和已经存在的CDockBar的属性进行相互设置。因为一个窗口的属性不仅仅和自身相关也和相应的父窗口相关,所以这里首先设置父窗口的属性,只要在父窗口支持的情况下,根据当前子窗口的属性更新父窗口,然后反过来根据父窗口的属性更新子窗口的属性。在窗口的属性更新之后需要设置CControlBar的位置,这需要根据传递进来的参数进行设置,如果参数为NULL,则设置为默认的值。在设置CControlBar之后需要更新其父窗口,并同时设置和当前的DockBar相关联。注意最后一个SetWindowPos函数调用传递的宽度和高度均为0,所以在这里窗口还不会被现实出来,下面会重新根据之前计算出来的数据排除掉和当前窗口占用统一位置的窗口,然后设置延迟计算的标志,等到合适的时候一次计算整个布局。
_AFXWIN_INLINE void CFrameWnd::DelayRecalcLayout(BOOL bNotify) { m_nIdleFlags |= (idleLayout | (bNotify ? idleNotify : 0)); };上面是创建浮动工具条的全过程,不过到目前位置还只是创建了窗口,并不能显示,如果需要显示需要一系列的触发。如果需要根据自己的选择来决定是否显示浮动条,那么就需要借助函数ShowControlBar的帮助,这个函数会根据参数来决定浮动条是否进行显示。
void CFrameWnd::ShowControlBar(CControlBar* pBar, BOOL bShow, BOOL bDelay) { CFrameWnd* pParentFrame = pBar->GetDockingFrame(); if (bDelay) { pBar->DelayShow(bShow); pParentFrame->DelayRecalcLayout(); } else { pBar->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE| (bShow ? SWP_SHOWWINDOW : SWP_HIDEWINDOW)); pBar->DelayShow(bShow); if (bShow || !pBar->IsFloating()) pParentFrame->RecalcLayout(FALSE); } if (pBar->IsFloating()) { int nVisCount = pBar->m_pDockBar != NULL ? pBar->m_pDockBar->GetDockedVisibleCount() : bShow ? 1 : 0; if (nVisCount == 1 && bShow) { pParentFrame->m_nShowDelay = -1; if (bDelay) { pParentFrame->m_nShowDelay = SW_SHOWNA; pParentFrame->RecalcLayout(FALSE); } else pParentFrame->ShowWindow(SW_SHOWNA); } else if (nVisCount == 0) { ASSERT(!bShow); pParentFrame->m_nShowDelay = -1; if (bDelay) pParentFrame->m_nShowDelay = SW_HIDE; else pParentFrame->ShowWindow(SW_HIDE); } else if (!bDelay) { pParentFrame->RecalcLayout(FALSE); } } }如果调用者要求延迟显示,那么仅仅设置延迟标志就可以了,否则会设置浮动条的显示属性,然后根据需要是否立即计算各个布局的矩形大小以便于接下来的显示操作。如果显示窗口是浮动的,那么可能没有DockBar,由于CControl是利用CDockBar和frame相关联的。如果不是浮动窗口,那么一定有DockBar,有DockBar就很简单了,这样就可以直接交给系统来处理,系统会发送显示消息,在这个消息的循环过程中所有的与主窗口相关的子窗口都会被刷新,这就是最后一条if语句所做的事。如果是浮动的并且没有DockBar那么就需要根据是否显示来做选择。如果bShow为真,则表明需要显示,然后根据bDelay决定是否刷新显示布局,如果bDelay为真,则表明稍后需要刷新窗口布局,否则系统会认为自己在多此一举,直接更新自身的显示就可以了。如果bShow不为真,则表明不需要延迟显示,那就各顾各家。主窗口显示自己就可以了。