004-08  前へ←  ホームへ  →次へ  004-番外2

No.004 - 番外1  ドッキング・ツールバー ちょっとだけ MFCソース解析


MFCドッキングツールバーのドッキング時の処理をちょっとだけ解析する。
MFCの複雑さが理解できると思う。
ソースはMicrosoft Visual Studio\VC98\MFC\SRCフォルダより抜粋

CControlBarInfo

CWnd
┣ CFrameWnd
┣ CToolBarCtrl
┗ CControlBar
   ┣ CDockBar
   ┗ CToolBar


ドッキングするとき各ウィンドウサイズとか
1親ウィンドウ変更
2各ウィンドウサイズ変更
 実行 実際に呼ばれる関数
CFrameWnd::DockControlBar CFrameWnd::DockControlBar
  CDockBar::DockControlBar CDockBar::DockControlBar
  pBar->SetParent(this)  // 親ウィンドウ変更
pFrameWnd->DelayRecalcLayout()
SetParent
CFrameWnd::DelayRecalcLayout()

CFrameWnd::OnIdleUpdateCmdUI  // スレッドのアイドリング(メッセージ・ループ) CFrameWnd::OnIdleUpdateCmdUI
CFrameWnd::RecalcLayout CFrameWnd::RecalcLayout
  CFrameWndのCWnd::RepositionBars CFrameWndのCWnd::RepositionBars
  layout.hDWP = (nFlags != reposQuery)? ::BeginDeferWindowPos(8) :  NULL;
::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&layout);

CDockBar::OnSizeParent(WPARAM wParam, LPARAM lParam) CDockBar::OnSizeParent(WPARAM wParam, LPARAM lParam)
  AFX_SIZEPARENTPARAMS* lpLayout = lParam;  // ウィンドウ・レイアウト構造体
CControlBar::OnSizeParent(WPARAM wParam, LPARAM lParam)

CControlBar::OnSizeParent(WPARAM wParam, LPARAM lParam)
  CDockBar::RecalcDelayShow CDockBar::RecalcDelayShow
  void AFXAPI AfxRepositionWindow void AFXAPI AfxRepositionWindow
  lpLayout->hDWP = ::DeferWindowPos(lpLayout->hDWP, hWnd, NULL,

CFrameWnd::DockControlBar で メインウィンドウ にツールバーを ドッキング する。
このCFrameWnd::DockControlBar関数はドッキング先となるCDockBarを選定しポインタ pDockBarを得て、
そのポインタからCDockBar::DockControlBarを呼ぶ。
よばれたCDockBar::DockControlBar関数ではドッキングさせるツールバーの親ウィンドウを変更し、
メインウィンドウのレイアウト変更フラグをセットする。

CFrameWnd::OnIdleUpdateCmdUIは処理メッセージがないとき(?)スレッドクラスから呼ばれる。
レイアウト変更フラグが変更されているときこの関数は、CFrameWnd::RecalcLayoutを呼ぶ。
さらにその中からCWnd::RepositionBarsを呼ぶ
CWnd::RepositionBarsは::BeginDeferWindowPos(8) でレイアウト構造体を得て、
各ウィンドウにWM_SIZEPARENTメッセージを送りウィンドウサイズを再計算させる。
WM_SIZEPARENTメッセージを送られたCDockBarはウィンドウ・レイアウト構造体を処理し基本クラス(CControlBar)のOnSizeParentを呼ぶ。
CControlBar::OnSizeParentではCDockBar::RecalcDelayShowを呼びウィンドウ表示状態を::DeferWindowPosでセットする。
CDockBar::RecalcDelayShowはさらにグローバル関数のAfxRepositionWindowを呼び、
AfxRepositionWindow:DeferWindowPosでウィンドウサイズを決定する。


include/afxwin2.inl

_AFXWIN_INLINE void CFrameWnd::DelayRecalcLayout(BOOL bNotify)
        { m_nIdleFlags |= (idleLayout | (bNotify ? idleNotify : 0)); };

winfrm.cpp
void CFrameWnd::OnIdleUpdateCmdUI()
{
        // update menu if necessary
        if (m_nIdleFlags & idleMenu)
                OnUpdateFrameMenu(m_hMenuAlt);

        // update title if necessary
        if (m_nIdleFlags & idleTitle)
                OnUpdateFrameTitle(TRUE);

        // recalc layout if necessary
        if (m_nIdleFlags & idleLayout)
        {
                RecalcLayout(m_nIdleFlags & idleNotify);
                UpdateWindow();
        }

        // set the current message string if necessary
        if (m_nIDTracking != m_nIDLastMessage)
        {
                SetMessageText(m_nIDTracking);
                ASSERT(m_nIDTracking == m_nIDLastMessage);
        }
        m_nIdleFlags = 0;
}

void CFrameWnd::RecalcLayout(BOOL bNotify)
{
        if (m_bInRecalcLayout)
                return;

        m_bInRecalcLayout = TRUE;
        // clear idle flags for recalc layout if called elsewhere
        if (m_nIdleFlags & idleNotify)
                bNotify = TRUE;
        m_nIdleFlags &= ~(idleLayout|idleNotify);

#ifndef _AFX_NO_OLE_SUPPORT
        // call the layout hook -- OLE support uses this hook
        if (bNotify && m_pNotifyHook != NULL)
                m_pNotifyHook->OnRecalcLayout();
#endif

        // reposition all the child windows (regardless of ID)
        if (GetStyle() & FWS_SNAPTOBARS)
        {
                CRect rect(0, 0, 32767, 32767);
                RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery,
                        &rect, &rect, FALSE);
                RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra,
                        &m_rectBorder, &rect, TRUE);
                CalcWindowRect(&rect);
                SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
                        SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
        }
        else
                RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, &m_rectBorder);
        m_bInRecalcLayout = FALSE;
}


winfrm2.cpp
void CFrameWnd::DockControlBar(CControlBar* pBar, UINT nDockBarID, LPCRECT lpRect)
{
        CDockBar* pDockBar = (nDockBarID == 0) ? NULL :
                (CDockBar*)GetControlBar(nDockBarID);
        DockControlBar(pBar, pDockBar, lpRect);
}

void CFrameWnd::DockControlBar(CControlBar* pBar, CDockBar* pDockBar, LPCRECT lpRect)
{
        ASSERT(pBar != NULL);
        // make sure CControlBar::EnableDocking has been called
        ASSERT(pBar->m_pDockContext != NULL);

        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]);
                                ASSERT(pDockBar != NULL);
                                // assert fails when initial CBRS_ of bar does not
                                // match available docking sites, as set by EnableDocking()
                                break;
                        }
                }
        }
        ASSERT(pDockBar != NULL);
        ASSERT(m_listControlBars.Find(pBar) != NULL);
        ASSERT(pBar->m_pDockSite == this);
        // if this assertion occurred it is because the parent of pBar was not initially
        // this CFrameWnd when pBar's OnCreate was called
        // i.e. this control bar should have been created with a different parent initially

        pDockBar->DockControlBar(pBar, lpRect);
}

wincore.cpp
void CWnd::RepositionBars(UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver,
        UINT nFlags, LPRECT lpRectParam, LPCRECT lpRectClient, BOOL bStretch)
{
        ASSERT(nFlags == 0 || nFlags == reposQuery || nFlags == reposExtra);

        // walk kids in order, control bars get the resize notification
        //   which allow them to shrink the client area
        // remaining size goes to the 'nIDLeftOver' pane
        // NOTE: nIDFirst->nIDLast are usually 0->0xffff

        AFX_SIZEPARENTPARAMS layout;
        HWND hWndLeftOver = NULL;

        layout.bStretch = bStretch;
        layout.sizeTotal.cx = layout.sizeTotal.cy = 0;
        if (lpRectClient != NULL)
                layout.rect = *lpRectClient;    // starting rect comes from parameter
        else
                GetClientRect(&layout.rect);    // starting rect comes from client rect

        if (nFlags != reposQuery)
                layout.hDWP = ::BeginDeferWindowPos(8); // reasonable guess
        else
                layout.hDWP = NULL; // not actually doing layout

        for (HWND hWndChild = ::GetTopWindow(m_hWnd); hWndChild != NULL;
                hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT))
        {
                UINT nIDC = _AfxGetDlgCtrlID(hWndChild);
                CWnd* pWnd = CWnd::FromHandlePermanent(hWndChild);
                if (nIDC == nIDLeftOver)
                        hWndLeftOver = hWndChild;
                else if (nIDC >= nIDFirst && nIDC <= nIDLast && pWnd != NULL)
                        ::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&layout);
        }

        // if just getting the available rectangle, return it now...
        if (nFlags == reposQuery)
        {
                ASSERT(lpRectParam != NULL);
                if (bStretch)
                        ::CopyRect(lpRectParam, &layout.rect);
                else
                {
                        lpRectParam->left = lpRectParam->top = 0;
                        lpRectParam->right = layout.sizeTotal.cx;
                        lpRectParam->bottom = layout.sizeTotal.cy;
                }
                return;
        }

        // the rest is the client size of the left-over pane
        if (nIDLeftOver != 0 && hWndLeftOver != NULL)
        {
                CWnd* pLeftOver = CWnd::FromHandle(hWndLeftOver);
                // allow extra space as specified by lpRectBorder
                if (nFlags == reposExtra)
                {
                        ASSERT(lpRectParam != NULL);
                        layout.rect.left += lpRectParam->left;
                        layout.rect.top += lpRectParam->top;
                        layout.rect.right -= lpRectParam->right;
                        layout.rect.bottom -= lpRectParam->bottom;
                }
                // reposition the window
                pLeftOver->CalcWindowRect(&layout.rect);
                AfxRepositionWindow(&layout, hWndLeftOver, &layout.rect);
        }

        // move and resize all the windows at once!
        if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP))
                TRACE0("Warning: DeferWindowPos failed - low system resources.\n");
}

void AFXAPI AfxRepositionWindow(AFX_SIZEPARENTPARAMS* lpLayout,
        HWND hWnd, LPCRECT lpRect)
{
        ASSERT(hWnd != NULL);
        ASSERT(lpRect != NULL);
        HWND hWndParent = ::GetParent(hWnd);
        ASSERT(hWndParent != NULL);

        if (lpLayout != NULL && lpLayout->hDWP == NULL)
                return;

        // first check if the new rectangle is the same as the current
        CRect rectOld;
        ::GetWindowRect(hWnd, rectOld);
        ::ScreenToClient(hWndParent, &rectOld.TopLeft());
        ::ScreenToClient(hWndParent, &rectOld.BottomRight());
        if (::EqualRect(rectOld, lpRect))
                return;     // nothing to do

        // try to use DeferWindowPos for speed, otherwise use SetWindowPos
        if (lpLayout != NULL)
        {
                lpLayout->hDWP = ::DeferWindowPos(lpLayout->hDWP, hWnd, NULL,
                        lpRect->left, lpRect->top,  lpRect->right - lpRect->left,
                        lpRect->bottom - lpRect->top, SWP_NOACTIVATE|SWP_NOZORDER);
        }
        else
        {
                ::SetWindowPos(hWnd, NULL, lpRect->left, lpRect->top,
                        lpRect->right - lpRect->left, lpRect->bottom - lpRect->top,
                        SWP_NOACTIVATE|SWP_NOZORDER);
        }
}

bardock.cpp
void CDockBar::DockControlBar(CControlBar* pBar, LPCRECT lpRect)
{
        ASSERT_VALID(this);
        ASSERT_VALID(pBar);
        ASSERT_KINDOF(CControlBar, pBar);

        CRect rectBar;
        pBar->GetWindowRect(&rectBar);
        if (pBar->m_pDockBar == this && (lpRect == NULL || rectBar == *lpRect))
        {
                // already docked and no change in position
                return;
        }

        // set CBRS_FLOAT_MULTI style if docking bar has it
        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);
        }

        // align correctly and turn on all borders
        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);

        // hide first if changing to a new docking site to avoid flashing
        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)
        {
                // insert into appropriate row
                CRect rect(lpRect);
                ScreenToClient(&rect);
                CPoint ptMid(rect.left + rect.Width()/2, rect.top + rect.Height()/2);
                nPos = Insert(pBar, rect, ptMid);

                // position at requested position
                pBar->SetWindowPos(NULL, rect.left, rect.top, rect.Width(),
                        rect.Height(), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS);
        }
        else
        {
                // always add on current row, then create new one
                m_arrBars.Add(pBar);
                m_arrBars.Add(NULL);

                // align off the edge initially
                pBar->SetWindowPos(NULL, -afxData.cxBorder2, -afxData.cyBorder2, 0, 0,
                        SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS);
        }

        // attach it to the docking site
        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)
        {
                ASSERT(!pBar->IsWindowVisible());
                pBar->SetWindowPos(NULL, 0, 0, 0, 0,
                        SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_SHOWWINDOW);
        }

        // remove any place holder for pBar in this dockbar
        RemovePlaceHolder(pBar);

        // get parent frame for recalc layout
        CFrameWnd* pFrameWnd = GetDockingFrame();
        pFrameWnd->DelayRecalcLayout();
}

LRESULT CDockBar::OnSizeParent(WPARAM wParam, LPARAM lParam)
{
        AFX_SIZEPARENTPARAMS* lpLayout = (AFX_SIZEPARENTPARAMS*)lParam;

        // set m_bLayoutQuery to TRUE if lpLayout->hDWP == NULL
        BOOL bLayoutQuery = m_bLayoutQuery;
        CRect rectLayout = m_rectLayout;
        m_bLayoutQuery = (lpLayout->hDWP == NULL);
        m_rectLayout = lpLayout->rect;
        LRESULT lResult = CControlBar::OnSizeParent(wParam, lParam);
        // restore m_bLayoutQuery
        m_bLayoutQuery = bLayoutQuery;
        m_rectLayout = rectLayout;

        return lResult;
}


barcore.cpp
DWORD CControlBar::RecalcDelayShow(AFX_SIZEPARENTPARAMS* lpLayout)
{
        ASSERT(lpLayout != NULL);

        // resize and reposition this control bar based on styles
        DWORD dwStyle = (m_dwStyle & (CBRS_ALIGN_ANY|CBRS_BORDER_ANY)) |
                (GetStyle() & WS_VISIBLE);

        // handle delay hide/show
        if (m_nStateFlags & (delayHide|delayShow))
        {
                UINT swpFlags = 0;
                if (m_nStateFlags & delayHide)
                {
                        ASSERT((m_nStateFlags & delayShow) == 0);
                        if (dwStyle & WS_VISIBLE)
                                swpFlags = SWP_HIDEWINDOW;
                }
                else
                {
                        ASSERT(m_nStateFlags & delayShow);
                        if ((dwStyle & WS_VISIBLE) == 0)
                                swpFlags = SWP_SHOWWINDOW;
                }
                if (swpFlags != 0)
                {
                        // make the window seem visible/hidden
                        dwStyle ^= WS_VISIBLE;
                        if (lpLayout->hDWP != NULL)
                        {
                                // clear delay flags
                                m_nStateFlags &= ~(delayShow|delayHide);
                                // hide/show the window if actually doing layout
                                lpLayout->hDWP = ::DeferWindowPos(lpLayout->hDWP, m_hWnd, NULL,
                                        0, 0, 0, 0, swpFlags|
                                        SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
                        }
                }
                else
                {
                        // clear delay flags -- window is already in correct state
                        m_nStateFlags &= ~(delayShow|delayHide);
                }
        }
        return dwStyle; // return new style
}

LRESULT CControlBar::OnSizeParent(WPARAM, LPARAM lParam)
{
        AFX_SIZEPARENTPARAMS* lpLayout = (AFX_SIZEPARENTPARAMS*)lParam;
        DWORD dwStyle = RecalcDelayShow(lpLayout);

        if ((dwStyle & WS_VISIBLE) && (dwStyle & CBRS_ALIGN_ANY) != 0)
        {
                // align the control bar
                CRect rect;
                rect.CopyRect(&lpLayout->rect);

                CSize sizeAvail = rect.Size();  // maximum size available

                // get maximum requested size
                DWORD dwMode = lpLayout->bStretch ? LM_STRETCH : 0;
                if ((m_dwStyle & CBRS_SIZE_DYNAMIC) && m_dwStyle & CBRS_FLOATING)
                        dwMode |= LM_HORZ | LM_MRUWIDTH;
                else if (dwStyle & CBRS_ORIENT_HORZ)
                        dwMode |= LM_HORZ | LM_HORZDOCK;
                else
                        dwMode |=  LM_VERTDOCK;

                CSize size = CalcDynamicLayout(-1, dwMode);

                size.cx = min(size.cx, sizeAvail.cx);
                size.cy = min(size.cy, sizeAvail.cy);

                if (dwStyle & CBRS_ORIENT_HORZ)
                {
                        lpLayout->sizeTotal.cy += size.cy;
                        lpLayout->sizeTotal.cx = max(lpLayout->sizeTotal.cx, size.cx);
                        if (dwStyle & CBRS_ALIGN_TOP)
                                lpLayout->rect.top += size.cy;
                        else if (dwStyle & CBRS_ALIGN_BOTTOM)
                        {
                                rect.top = rect.bottom - size.cy;
                                lpLayout->rect.bottom -= size.cy;
                        }
                }
                else if (dwStyle & CBRS_ORIENT_VERT)
                {
                        lpLayout->sizeTotal.cx += size.cx;
                        lpLayout->sizeTotal.cy = max(lpLayout->sizeTotal.cy, size.cy);
                        if (dwStyle & CBRS_ALIGN_LEFT)
                                lpLayout->rect.left += size.cx;
                        else if (dwStyle & CBRS_ALIGN_RIGHT)
                        {
                                rect.left = rect.right - size.cx;
                                lpLayout->rect.right -= size.cx;
                        }
                }
                else
                {
                        ASSERT(FALSE);      // can never happen
                }

                rect.right = rect.left + size.cx;
                rect.bottom = rect.top + size.cy;

                // only resize the window if doing layout and not just rect query
                if (lpLayout->hDWP != NULL)
                        AfxRepositionWindow(lpLayout, m_hWnd, &rect);
        }
        return 0;
}


 004-08  前へ←  ホームへ  →次へ  004-番外2