004-番外1  前へ←  ホームへ  →次へ  005-01

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


CDockContextについて解説しているサイトがあまりにも少ないので
MFCドッキングツールバーのドラッグ&ドロップちょっとだけ解析する。
ソースはMicrosoft Visual Studio\VC98\MFC\SRCフォルダより抜粋

ドラッグ&ドロップ
実行 実際に呼ばれる関数

void CControlBar::OnLButtonDown(UINT nFlags, CPoint pt)

CControlBar::OnLButtonDown
m_pDockContext->StartDrag(pt); CDockContext::StartDrag
InitLoop(); InitLoop();
m_pBar->GetWindowRect(rect);
m_rectFrameDragHorz,  m_rectFrameDragVertの計算
m_dwOverDockStyle = CanDock(); CDockContext::CanDock
Move(pt); CDockContext::Move
Track(); CDockContext::Track
m_pBar->SetCapture();
while (CWnd::GetCapture() == m_pBar){
case WM_LBUTTONUP:  EndDrag(); return TRUE; CDockContext::EndDrag
case WM_MOUSEMOVE   Move(msg.pt);  break; CDockContext::Move
............
default:
DispatchMessage(&msg);
}
CancelLoop(); CDockContext::CancelLoop

ツールバーなどのCControlBarクラスはマウス左ボタンの押下を検知すると
CControlBar::OnLButtonDownでマウス座標からドラッグ開始かどうかの判定をする。
ドラッグ開始ならばメンバ変数のm_pDockContextからCDockContext::StartDragを呼ぶ。

StartDragドラッグ終了までの全ての関数を内部から呼び出す。
InitLoop関数は描画の終わっていないWM_PAINTメッセージ吐き出させる。
そしてドラッグ中の描画処理のためにデスクトップDCを取得する。
StartDragは続いてウィンドウ判定範囲の設定をする。
m_rectFrameDragHorz,  m_rectFrameDragVertにいろいろ計算をしているが、実際何の計算かよくわからない。

m_dwOverDockStyleには現在のドッキング判定フラグCanDock関数の返り値を代入する。
Track関数はSetCapture()でマウスをキャプチャーし、WinMainで行うようなメッセージループを内部で実行する。
ここでマウス・メッセージを制御する。
マウスが移動したら Move関数を呼びドッキング判定フラグm_dwOverDockStyle =CanDockを更新し、ドラッグを示す矩形を描画する。
マウス左ボタンを離したらEndDrag関数でDCメモリの解放等の終了処理を行い、ループを抜ける。

以下のファイルは
Microsoft Visual Studio\VC98\MFC\SRCフォルダ
にあります

barcore.cpp
void CControlBar::OnLButtonDown(UINT nFlags, CPoint pt)
{
        // only start dragging if clicked in "void" space
        if (m_pDockBar != NULL && OnToolHitTest(pt, NULL) == -1)
        {
                // start the drag
                ASSERT(m_pDockContext != NULL);
                ClientToScreen(&pt);
                m_pDockContext->StartDrag(pt);
        }
        else
        {
                CWnd::OnLButtonDown(nFlags, pt);
        }
}

dockcont.cpp

void CDockContext::StartDrag(CPoint pt)
{
        ASSERT_VALID(m_pBar);
        m_bDragging = TRUE;

        InitLoop();

        // GetWindowRect returns screen coordinates(not mirrored),
        // so if the desktop is mirrored then turn off mirroring
        // for the desktop dc so that we get correct focus rect drawn.
        // This layout change should be remembered, just in case ...

        if (m_pDC->GetLayout() & LAYOUT_RTL)
                m_pDC->SetLayout(LAYOUT_LTR);

        if (m_pBar->m_dwStyle & CBRS_SIZE_DYNAMIC)
        {
                // get true bar size (including borders)
                CRect rect;
                m_pBar->GetWindowRect(rect);
                m_ptLast = pt;
                CSize sizeHorz = m_pBar->CalcDynamicLayout(0, LM_HORZ | LM_HORZDOCK);
                CSize sizeVert = m_pBar->CalcDynamicLayout(0, LM_VERTDOCK);
                CSize sizeFloat = m_pBar->CalcDynamicLayout(0, LM_HORZ | LM_MRUWIDTH);

                m_rectDragHorz = CRect(rect.TopLeft(), sizeHorz);
                m_rectDragVert = CRect(rect.TopLeft(), sizeVert);

                // calculate frame dragging rectangle
                m_rectFrameDragHorz = CRect(rect.TopLeft(), sizeFloat);
                m_rectFrameDragVert = CRect(rect.TopLeft(), sizeFloat);

                CMiniFrameWnd::CalcBorders(&m_rectFrameDragHorz);
                CMiniFrameWnd::CalcBorders(&m_rectFrameDragVert);

                m_rectFrameDragHorz.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2);
                m_rectFrameDragVert.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2);
        }
        else if (m_pBar->m_dwStyle & CBRS_SIZE_FIXED)
        {
                // get true bar size (including borders)
                CRect rect;
                m_pBar->GetWindowRect(rect);
                m_ptLast = pt;
                CSize sizeHorz = m_pBar->CalcDynamicLayout(-1, LM_HORZ | LM_HORZDOCK);
                CSize sizeVert = m_pBar->CalcDynamicLayout(-1, LM_VERTDOCK);

                // calculate frame dragging rectangle
                m_rectFrameDragHorz = m_rectDragHorz = CRect(rect.TopLeft(), sizeHorz);
                m_rectFrameDragVert = m_rectDragVert = CRect(rect.TopLeft(), sizeVert);

                CMiniFrameWnd::CalcBorders(&m_rectFrameDragHorz);
                CMiniFrameWnd::CalcBorders(&m_rectFrameDragVert);
                m_rectFrameDragHorz.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2);
                m_rectFrameDragVert.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2);
        }
        else
        {
                // get true bar size (including borders)
                CRect rect;
                m_pBar->GetWindowRect(rect);
                m_ptLast = pt;
                BOOL bHorz = HORZF(m_dwStyle);
                DWORD dwMode = !bHorz ? (LM_HORZ | LM_HORZDOCK) : LM_VERTDOCK;
                CSize size = m_pBar->CalcDynamicLayout(-1, dwMode);

                // calculate inverted dragging rect
                if (bHorz)
                {
                        m_rectDragHorz = rect;
                        m_rectDragVert = CRect(CPoint(pt.x - rect.Height()/2, rect.top), size);
                }
                else // vertical orientation
                {
                        m_rectDragVert = rect;
                        m_rectDragHorz = CRect(CPoint(rect.left, pt.y - rect.Width()/2), size);
                }

                // calculate frame dragging rectangle
                m_rectFrameDragHorz = m_rectDragHorz;
                m_rectFrameDragVert = m_rectDragVert;

                CMiniFrameWnd::CalcBorders(&m_rectFrameDragHorz);
                CMiniFrameWnd::CalcBorders(&m_rectFrameDragVert);
                m_rectFrameDragHorz.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2);
                m_rectFrameDragVert.InflateRect(-afxData.cxBorder2, -afxData.cyBorder2);
        }

        // adjust rectangles so that point is inside
        _AfxAdjustRectangle(m_rectDragHorz, pt);
        _AfxAdjustRectangle(m_rectDragVert, pt);
        _AfxAdjustRectangle(m_rectFrameDragHorz, pt);
        _AfxAdjustRectangle(m_rectFrameDragVert, pt);

        // initialize tracking state and enter tracking loop
        m_dwOverDockStyle = CanDock();
        Move(pt);   // call it here to handle special keys
        Track();
}

void CDockContext::Move(CPoint pt)
{
        CPoint ptOffset = pt - m_ptLast;

        // offset all drag rects to new position
        m_rectDragHorz.OffsetRect(ptOffset);
        m_rectFrameDragHorz.OffsetRect(ptOffset);
        m_rectDragVert.OffsetRect(ptOffset);
        m_rectFrameDragVert.OffsetRect(ptOffset);
        m_ptLast = pt;

        // if control key is down don't dock
        m_dwOverDockStyle = m_bForceFrame ? 0 : CanDock();

        // update feedback
        DrawFocusRect();
}

void CDockContext::EndDrag()
{
        CancelLoop();

        if (m_dwOverDockStyle != 0)
        {
                CDockBar* pDockBar = GetDockBar(m_dwOverDockStyle);
                ASSERT(pDockBar != NULL);

                CRect rect = (m_dwOverDockStyle & CBRS_ORIENT_VERT) ?
                        m_rectDragVert : m_rectDragHorz;

                UINT uID = _AfxGetDlgCtrlID(pDockBar->m_hWnd);
                if (uID >= AFX_IDW_DOCKBAR_TOP &&
                        uID <= AFX_IDW_DOCKBAR_BOTTOM)
                {
                        m_uMRUDockID = uID;
                        m_rectMRUDockPos = rect;
                        pDockBar->ScreenToClient(&m_rectMRUDockPos);
                }

                // dock it at the specified position, RecalcLayout will snap
                m_pDockSite->DockControlBar(m_pBar, pDockBar, &rect);
                m_pDockSite->RecalcLayout();
        }
        else if ((m_dwStyle & CBRS_SIZE_DYNAMIC) || (HORZF(m_dwStyle) && !m_bFlip) ||
                        (VERTF(m_dwStyle) && m_bFlip))
        {
                m_dwMRUFloatStyle = CBRS_ALIGN_TOP | (m_dwDockStyle & CBRS_FLOAT_MULTI);
                m_ptMRUFloatPos = m_rectFrameDragHorz.TopLeft();
                m_pDockSite->FloatControlBar(m_pBar, m_ptMRUFloatPos, m_dwMRUFloatStyle);
        }
        else // vertical float
        {
                m_dwMRUFloatStyle = CBRS_ALIGN_LEFT | (m_dwDockStyle & CBRS_FLOAT_MULTI);
                m_ptMRUFloatPos = m_rectFrameDragVert.TopLeft();
                m_pDockSite->FloatControlBar(m_pBar, m_ptMRUFloatPos, m_dwMRUFloatStyle);
        }
}

void CDockContext::InitLoop()
{
        // handle pending WM_PAINT messages
        MSG msg;
        while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
        {
                if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
                        return;
                DispatchMessage(&msg);
        }

        // get styles from bar
        m_dwDockStyle = m_pBar->m_dwDockStyle;
        m_dwStyle = m_pBar->m_dwStyle & CBRS_ALIGN_ANY;
        ASSERT(m_dwStyle != 0);

        // initialize state
        m_rectLast.SetRectEmpty();
        m_sizeLast.cx = m_sizeLast.cy = 0;
        m_bForceFrame = m_bFlip = m_bDitherLast = FALSE;

        // lock window update while dragging
        ASSERT(m_pDC == NULL);
        CWnd* pWnd = CWnd::GetDesktopWindow();
        if (pWnd->LockWindowUpdate())
                m_pDC = pWnd->GetDCEx(NULL, DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE);
        else
                m_pDC = pWnd->GetDCEx(NULL, DCX_WINDOW|DCX_CACHE);
        ASSERT(m_pDC != NULL);
}

void CDockContext::CancelLoop()
{
        DrawFocusRect(TRUE);    // gets rid of focus rect
        ReleaseCapture();

        CWnd* pWnd = CWnd::GetDesktopWindow();
        pWnd->UnlockWindowUpdate();
        if (m_pDC != NULL)
        {
                pWnd->ReleaseDC(m_pDC);
                m_pDC = NULL;
        }
}

void CDockContext::DrawFocusRect(BOOL bRemoveRect)
{
        ASSERT(m_pDC != NULL);

        // default to thin frame
        CSize size(CX_BORDER, CY_BORDER);

        // determine new rect and size
        CRect rect;
        CBrush* pWhiteBrush = CBrush::FromHandle((HBRUSH)::GetStockObject(WHITE_BRUSH));
        CBrush* pDitherBrush = CDC::GetHalftoneBrush();
        CBrush* pBrush = pWhiteBrush;

        if (HORZF(m_dwOverDockStyle))
                rect = m_rectDragHorz;
        else if (VERTF(m_dwOverDockStyle))
                rect = m_rectDragVert;
        else
        {
                // use thick frame instead
                size.cx = GetSystemMetrics(SM_CXFRAME) - CX_BORDER;
                size.cy = GetSystemMetrics(SM_CYFRAME) - CY_BORDER;
                if ((HORZF(m_dwStyle) && !m_bFlip) || (VERTF(m_dwStyle) && m_bFlip))
                        rect = m_rectFrameDragHorz;
                else
                        rect = m_rectFrameDragVert;
                pBrush = pDitherBrush;
        }
        if (bRemoveRect)
                size.cx = size.cy = 0;

        if (afxData.bWin4 &&
                (HORZF(m_dwOverDockStyle) || VERTF(m_dwOverDockStyle)))
        {
                // looks better one pixel in (makes the bar look pushed down)
                rect.InflateRect(-CX_BORDER, -CY_BORDER);
        }

        // draw it and remember last size
        m_pDC->DrawDragRect(&rect, size, &m_rectLast, m_sizeLast,
                pBrush, m_bDitherLast ? pDitherBrush : pWhiteBrush);
        m_rectLast = rect;
        m_sizeLast = size;
        m_bDitherLast = (pBrush == pDitherBrush);
}

DWORD CDockContext::CanDock()
{
        BOOL bStyleHorz;
        DWORD dwDock = 0; // Dock Canidate
        DWORD dwCurr = 0; // Current Orientation

        // let's check for something in our current orientation first
        // then if the shift key is not forcing our orientation then
        // check for horizontal or vertical orientations as long
        // as we are close enough
        ASSERT(m_dwStyle != 0);

        bStyleHorz = HORZF(m_dwStyle);
        bStyleHorz = m_bFlip ? !bStyleHorz : bStyleHorz;

        if (bStyleHorz && HORZF(m_dwDockStyle))
                dwDock = m_pDockSite->CanDock(m_rectDragHorz,
                                                                          m_dwDockStyle & ~CBRS_ORIENT_VERT);
        else if (VERTF(m_dwDockStyle))
                dwDock = m_pDockSite->CanDock(m_rectDragVert,
                                                                          m_dwDockStyle & ~CBRS_ORIENT_HORZ);

        if (!m_bFlip)
        {
                if (dwDock == 0 && HORZF(m_dwDockStyle))
                {
                        dwCurr = m_pDockSite->CanDock(m_rectDragVert,
                                                                                  m_dwDockStyle & ~CBRS_ORIENT_VERT);
                        dwDock = m_pDockSite->CanDock(m_rectDragHorz,
                                                                                  m_dwDockStyle & ~CBRS_ORIENT_VERT);
                        dwDock = (dwDock == dwCurr) ? dwDock : 0;
                }
                if (dwDock == 0 && VERTF(m_dwDockStyle))
                {
                        dwCurr = m_pDockSite->CanDock(m_rectDragHorz,
                                                                                  m_dwDockStyle & ~CBRS_ORIENT_HORZ);
                        dwDock = m_pDockSite->CanDock(m_rectDragVert,
                                                                                  m_dwDockStyle & ~CBRS_ORIENT_HORZ);
                        dwDock = (dwDock == dwCurr) ? dwDock : 0;
                }
        }

        return dwDock;
}

BOOL CDockContext::Track()
{
        // don't handle if capture already set
        if (::GetCapture() != NULL)
                return FALSE;

        // set capture to the window which received this message
        m_pBar->SetCapture();
        ASSERT(m_pBar == CWnd::GetCapture());

        // get messages until capture lost or cancelled/accepted
        while (CWnd::GetCapture() == m_pBar)
        {
                MSG msg;
                if (!::GetMessage(&msg, NULL, 0, 0))
                {
                        AfxPostQuitMessage(msg.wParam);
                        break;
                }

                switch (msg.message)
                {
                case WM_LBUTTONUP:
                        if (m_bDragging)
                                EndDrag();
                        else
                                EndResize();
                        return TRUE;
                case WM_MOUSEMOVE:
                        if (m_bDragging)
                                Move(msg.pt);
                        else
                                Stretch(msg.pt);
                        break;
                case WM_KEYUP:
                        if (m_bDragging)
                                OnKey((int)msg.wParam, FALSE);
                        break;
                case WM_KEYDOWN:
                        if (m_bDragging)
                                OnKey((int)msg.wParam, TRUE);
                        if (msg.wParam == VK_ESCAPE)
                        {
                                CancelLoop();
                                return FALSE;
                        }
                        break;
                case WM_RBUTTONDOWN:
                        CancelLoop();
                        return FALSE;

                // just dispatch rest of the messages
                default:
                        DispatchMessage(&msg);
                        break;
                }
        }

        CancelLoop();

        return FALSE;
}


 004-番外1  前へ←  ホームへ  →次へ  005-01