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; } |