// Windows Template Library - WTL version 10.0 // Copyright (C) Microsoft Corporation, WTL Team. All rights reserved. // // This file is a part of the Windows Template Library. // The use and distribution terms for this software are covered by the // Microsoft Public License (http://opensource.org/licenses/MS-PL) // which can be found in the file MS-PL.txt at the root folder. #ifndef __ATLSPLIT_H__ #define __ATLSPLIT_H__ #pragma once #ifndef __ATLAPP_H__ #error atlsplit.h requires atlapp.h to be included first #endif #ifndef __ATLWIN_H__ #error atlsplit.h requires atlwin.h to be included first #endif /////////////////////////////////////////////////////////////////////////////// // Classes in this file: // // CSplitterImpl // CSplitterWindowImpl // CSplitterWindowT - CSplitterWindow, CHorSplitterWindow namespace WTL { /////////////////////////////////////////////////////////////////////////////// // CSplitterImpl - Provides splitter support to any window // Splitter panes constants #define SPLIT_PANE_LEFT 0 #define SPLIT_PANE_RIGHT 1 #define SPLIT_PANE_TOP SPLIT_PANE_LEFT #define SPLIT_PANE_BOTTOM SPLIT_PANE_RIGHT #define SPLIT_PANE_NONE -1 // Splitter extended styles #define SPLIT_PROPORTIONAL 0x00000001 #define SPLIT_NONINTERACTIVE 0x00000002 #define SPLIT_RIGHTALIGNED 0x00000004 #define SPLIT_BOTTOMALIGNED SPLIT_RIGHTALIGNED #define SPLIT_GRADIENTBAR 0x00000008 #define SPLIT_FLATBAR 0x00000020 #define SPLIT_FIXEDBARSIZE 0x00000010 // Note: SPLIT_PROPORTIONAL and SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED are // mutually exclusive. If both are set, splitter defaults to SPLIT_PROPORTIONAL. // Also, SPLIT_FLATBAR overrides SPLIT_GRADIENTBAR if both are set. template class CSplitterImpl { public: enum { m_nPanesCount = 2, m_nPropMax = INT_MAX, m_cxyStep = 10 }; bool m_bVertical; HWND m_hWndPane[m_nPanesCount]; RECT m_rcSplitter; int m_xySplitterPos; // splitter bar position int m_xySplitterPosNew; // internal - new position while moving HWND m_hWndFocusSave; int m_nDefActivePane; int m_cxySplitBar; // splitter bar width/height HCURSOR m_hCursor; int m_cxyMin; // minimum pane size int m_cxyBarEdge; // splitter bar edge bool m_bFullDrag; int m_cxyDragOffset; // internal int m_nProportionalPos; bool m_bUpdateProportionalPos; DWORD m_dwExtendedStyle; // splitter specific extended styles int m_nSinglePane; // single pane mode int m_xySplitterDefPos; // default position bool m_bProportionalDefPos; // porportinal def pos // Constructor CSplitterImpl(bool bVertical = true) : m_bVertical(bVertical), m_xySplitterPos(-1), m_xySplitterPosNew(-1), m_hWndFocusSave(NULL), m_nDefActivePane(SPLIT_PANE_NONE), m_cxySplitBar(4), m_hCursor(NULL), m_cxyMin(0), m_cxyBarEdge(0), m_bFullDrag(true), m_cxyDragOffset(0), m_nProportionalPos(0), m_bUpdateProportionalPos(true), m_dwExtendedStyle(SPLIT_PROPORTIONAL), m_nSinglePane(SPLIT_PANE_NONE), m_xySplitterDefPos(-1), m_bProportionalDefPos(false) { m_hWndPane[SPLIT_PANE_LEFT] = NULL; m_hWndPane[SPLIT_PANE_RIGHT] = NULL; ::SetRectEmpty(&m_rcSplitter); } // Attributes void SetSplitterRect(LPRECT lpRect = NULL, bool bUpdate = true) { if(lpRect == NULL) { T* pT = static_cast(this); pT->GetClientRect(&m_rcSplitter); } else { m_rcSplitter = *lpRect; } if(IsProportional()) UpdateProportionalPos(); else if(IsRightAligned()) UpdateRightAlignPos(); if(bUpdate) UpdateSplitterLayout(); } void GetSplitterRect(LPRECT lpRect) const { ATLASSERT(lpRect != NULL); *lpRect = m_rcSplitter; } bool SetSplitterPos(int xyPos = -1, bool bUpdate = true) { if(xyPos == -1) // -1 == default position { if(m_bProportionalDefPos) { ATLASSERT((m_xySplitterDefPos >= 0) && (m_xySplitterDefPos <= m_nPropMax)); if(m_bVertical) xyPos = ::MulDiv(m_xySplitterDefPos, m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge, m_nPropMax); else xyPos = ::MulDiv(m_xySplitterDefPos, m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge, m_nPropMax); } else if(m_xySplitterDefPos != -1) { xyPos = m_xySplitterDefPos; } else // not set, use middle position { if(m_bVertical) xyPos = (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) / 2; else xyPos = (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge) / 2; } } // Adjust if out of valid range int cxyMax = 0; if(m_bVertical) cxyMax = m_rcSplitter.right - m_rcSplitter.left; else cxyMax = m_rcSplitter.bottom - m_rcSplitter.top; if(xyPos < m_cxyMin + m_cxyBarEdge) xyPos = m_cxyMin; else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin)) xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin; // Set new position and update if requested bool bRet = (m_xySplitterPos != xyPos); m_xySplitterPos = xyPos; if(m_bUpdateProportionalPos) { if(IsProportional()) StoreProportionalPos(); else if(IsRightAligned()) StoreRightAlignPos(); } else { m_bUpdateProportionalPos = true; } if(bUpdate && bRet) UpdateSplitterLayout(); return bRet; } int GetSplitterPos() const { return m_xySplitterPos; } void SetSplitterPosPct(int nPct, bool bUpdate = true) { ATLASSERT((nPct >= 0) && (nPct <= 100)); m_nProportionalPos = ::MulDiv(nPct, m_nPropMax, 100); UpdateProportionalPos(); if(bUpdate) UpdateSplitterLayout(); } int GetSplitterPosPct() const { int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); return ((cxyTotal > 0) && (m_xySplitterPos >= 0)) ? ::MulDiv(m_xySplitterPos, 100, cxyTotal) : -1; } bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE) { ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT) || (nPane == SPLIT_PANE_NONE)); if(!((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT) || (nPane == SPLIT_PANE_NONE))) return false; if(nPane != SPLIT_PANE_NONE) { if(::IsWindowVisible(m_hWndPane[nPane]) == FALSE) ::ShowWindow(m_hWndPane[nPane], SW_SHOW); int nOtherPane = (nPane == SPLIT_PANE_LEFT) ? SPLIT_PANE_RIGHT : SPLIT_PANE_LEFT; ::ShowWindow(m_hWndPane[nOtherPane], SW_HIDE); if(m_nDefActivePane != nPane) m_nDefActivePane = nPane; } else if(m_nSinglePane != SPLIT_PANE_NONE) { int nOtherPane = (m_nSinglePane == SPLIT_PANE_LEFT) ? SPLIT_PANE_RIGHT : SPLIT_PANE_LEFT; ::ShowWindow(m_hWndPane[nOtherPane], SW_SHOW); } m_nSinglePane = nPane; UpdateSplitterLayout(); return true; } int GetSinglePaneMode() const { return m_nSinglePane; } DWORD GetSplitterExtendedStyle() const { return m_dwExtendedStyle; } DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0) { DWORD dwPrevStyle = m_dwExtendedStyle; if(dwMask == 0) m_dwExtendedStyle = dwExtendedStyle; else m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask); #ifdef _DEBUG if(IsProportional() && IsRightAligned()) ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::SetSplitterExtendedStyle - SPLIT_PROPORTIONAL and SPLIT_RIGHTALIGNED are mutually exclusive, defaulting to SPLIT_PROPORTIONAL.\n")); #endif // _DEBUG return dwPrevStyle; } void SetSplitterDefaultPos(int xyPos = -1) { m_xySplitterDefPos = xyPos; m_bProportionalDefPos = false; } void SetSplitterDefaultPosPct(int nPct) { ATLASSERT((nPct >= 0) && (nPct <= 100)); m_xySplitterDefPos = ::MulDiv(nPct, m_nPropMax, 100); m_bProportionalDefPos = true; } // Splitter operations void SetSplitterPanes(HWND hWndLeftTop, HWND hWndRightBottom, bool bUpdate = true) { m_hWndPane[SPLIT_PANE_LEFT] = hWndLeftTop; m_hWndPane[SPLIT_PANE_RIGHT] = hWndRightBottom; ATLASSERT((m_hWndPane[SPLIT_PANE_LEFT] == NULL) || (m_hWndPane[SPLIT_PANE_RIGHT] == NULL) || (m_hWndPane[SPLIT_PANE_LEFT] != m_hWndPane[SPLIT_PANE_RIGHT])); if(bUpdate) UpdateSplitterLayout(); } bool SetSplitterPane(int nPane, HWND hWnd, bool bUpdate = true) { ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) return false; m_hWndPane[nPane] = hWnd; ATLASSERT((m_hWndPane[SPLIT_PANE_LEFT] == NULL) || (m_hWndPane[SPLIT_PANE_RIGHT] == NULL) || (m_hWndPane[SPLIT_PANE_LEFT] != m_hWndPane[SPLIT_PANE_RIGHT])); if(bUpdate) UpdateSplitterLayout(); return true; } HWND GetSplitterPane(int nPane) const { ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) return NULL; return m_hWndPane[nPane]; } bool SetActivePane(int nPane) { ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) return false; if((m_nSinglePane != SPLIT_PANE_NONE) && (nPane != m_nSinglePane)) return false; ::SetFocus(m_hWndPane[nPane]); m_nDefActivePane = nPane; return true; } int GetActivePane() const { int nRet = SPLIT_PANE_NONE; HWND hWndFocus = ::GetFocus(); if(hWndFocus != NULL) { for(int nPane = 0; nPane < m_nPanesCount; nPane++) { if((hWndFocus == m_hWndPane[nPane]) || (::IsChild(m_hWndPane[nPane], hWndFocus) != FALSE)) { nRet = nPane; break; } } } return nRet; } bool ActivateNextPane(bool bNext = true) { int nPane = m_nSinglePane; if(nPane == SPLIT_PANE_NONE) { switch(GetActivePane()) { case SPLIT_PANE_LEFT: nPane = SPLIT_PANE_RIGHT; break; case SPLIT_PANE_RIGHT: nPane = SPLIT_PANE_LEFT; break; default: nPane = bNext ? SPLIT_PANE_LEFT : SPLIT_PANE_RIGHT; break; } } return SetActivePane(nPane); } bool SetDefaultActivePane(int nPane) { ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT)) return false; m_nDefActivePane = nPane; return true; } bool SetDefaultActivePane(HWND hWnd) { for(int nPane = 0; nPane < m_nPanesCount; nPane++) { if(hWnd == m_hWndPane[nPane]) { m_nDefActivePane = nPane; return true; } } return false; // not found } int GetDefaultActivePane() const { return m_nDefActivePane; } void DrawSplitter(CDCHandle dc) { ATLASSERT(dc.m_hDC != NULL); if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1)) return; T* pT = static_cast(this); if(m_nSinglePane == SPLIT_PANE_NONE) { pT->DrawSplitterBar(dc); for(int nPane = 0; nPane < m_nPanesCount; nPane++) { if(m_hWndPane[nPane] == NULL) pT->DrawSplitterPane(dc, nPane); } } else { if(m_hWndPane[m_nSinglePane] == NULL) pT->DrawSplitterPane(dc, m_nSinglePane); } } // call to initiate moving splitter bar with keyboard void MoveSplitterBar() { T* pT = static_cast(this); int x = 0; int y = 0; if(m_bVertical) { x = m_xySplitterPos + (m_cxySplitBar / 2) + m_cxyBarEdge; y = (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge) / 2; } else { x = (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) / 2; y = m_xySplitterPos + (m_cxySplitBar / 2) + m_cxyBarEdge; } POINT pt = { x, y }; pT->ClientToScreen(&pt); ::SetCursorPos(pt.x, pt.y); m_xySplitterPosNew = m_xySplitterPos; pT->SetCapture(); m_hWndFocusSave = pT->SetFocus(); ::SetCursor(m_hCursor); if(!m_bFullDrag) DrawGhostBar(); if(m_bVertical) m_cxyDragOffset = x - m_rcSplitter.left - m_xySplitterPos; else m_cxyDragOffset = y - m_rcSplitter.top - m_xySplitterPos; } void SetOrientation(bool bVertical, bool bUpdate = true) { if(m_bVertical != bVertical) { m_bVertical = bVertical; m_hCursor = ::LoadCursor(NULL, m_bVertical ? IDC_SIZEWE : IDC_SIZENS); T* pT = static_cast(this); pT->GetSystemSettings(false); if(m_bVertical) m_xySplitterPos = ::MulDiv(m_xySplitterPos, m_rcSplitter.right - m_rcSplitter.left, m_rcSplitter.bottom - m_rcSplitter.top); else m_xySplitterPos = ::MulDiv(m_xySplitterPos, m_rcSplitter.bottom - m_rcSplitter.top, m_rcSplitter.right - m_rcSplitter.left); } if(bUpdate) UpdateSplitterLayout(); } // Overrideables void DrawSplitterBar(CDCHandle dc) { RECT rect = {}; if(GetSplitterBarRect(&rect)) { dc.FillRect(&rect, COLOR_3DFACE); if((m_dwExtendedStyle & SPLIT_FLATBAR) != 0) { RECT rect1 = rect; if(m_bVertical) rect1.right = rect1.left + 1; else rect1.bottom = rect1.top + 1; dc.FillRect(&rect1, COLOR_WINDOW); rect1 = rect; if(m_bVertical) rect1.left = rect1.right - 1; else rect1.top = rect1.bottom - 1; dc.FillRect(&rect1, COLOR_3DSHADOW); } else if((m_dwExtendedStyle & SPLIT_GRADIENTBAR) != 0) { RECT rect2 = rect; if(m_bVertical) rect2.left = (rect.left + rect.right) / 2 - 1; else rect2.top = (rect.top + rect.bottom) / 2 - 1; dc.GradientFillRect(rect2, ::GetSysColor(COLOR_3DFACE), ::GetSysColor(COLOR_3DSHADOW), m_bVertical); } // draw 3D edge if needed T* pT = static_cast(this); if((pT->GetExStyle() & WS_EX_CLIENTEDGE) != 0) dc.DrawEdge(&rect, EDGE_RAISED, m_bVertical ? (BF_LEFT | BF_RIGHT) : (BF_TOP | BF_BOTTOM)); } } // called only if pane is empty void DrawSplitterPane(CDCHandle dc, int nPane) { RECT rect = {}; if(GetSplitterPaneRect(nPane, &rect)) { T* pT = static_cast(this); if((pT->GetExStyle() & WS_EX_CLIENTEDGE) == 0) dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); dc.FillRect(&rect, COLOR_APPWORKSPACE); } } // Message map and handlers BEGIN_MSG_MAP(CSplitterImpl) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint) if(IsInteractive()) { MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor) MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp) MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDoubleClick) MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged) MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown) } MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate) MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange) END_MSG_MAP() LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { T* pT = static_cast(this); pT->Init(); bHandled = FALSE; return 1; } LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { T* pT = static_cast(this); // try setting position if not set if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1)) pT->SetSplitterPos(); // do painting if(wParam != NULL) { pT->DrawSplitter((HDC)wParam); } else { CPaintDC dc(pT->m_hWnd); pT->DrawSplitter(dc.m_hDC); } return 0; } LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { T* pT = static_cast(this); if(((HWND)wParam == pT->m_hWnd) && (LOWORD(lParam) == HTCLIENT)) { DWORD dwPos = ::GetMessagePos(); POINT ptPos = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) }; pT->ScreenToClient(&ptPos); if(IsOverSplitterBar(ptPos.x, ptPos.y)) return 1; } bHandled = FALSE; return 0; } LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { T* pT = static_cast(this); int xPos = GET_X_LPARAM(lParam); int yPos = GET_Y_LPARAM(lParam); if(::GetCapture() == pT->m_hWnd) { int xyNewSplitPos = 0; if(m_bVertical) xyNewSplitPos = xPos - m_rcSplitter.left - m_cxyDragOffset; else xyNewSplitPos = yPos - m_rcSplitter.top - m_cxyDragOffset; if(xyNewSplitPos == -1) // avoid -1, that means default position xyNewSplitPos = -2; if(m_xySplitterPos != xyNewSplitPos) { if(m_bFullDrag) { if(pT->SetSplitterPos(xyNewSplitPos, true)) pT->UpdateWindow(); } else { DrawGhostBar(); pT->SetSplitterPos(xyNewSplitPos, false); DrawGhostBar(); } } } else // not dragging, just set cursor { if(IsOverSplitterBar(xPos, yPos)) ::SetCursor(m_hCursor); bHandled = FALSE; } return 0; } LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { T* pT = static_cast(this); int xPos = GET_X_LPARAM(lParam); int yPos = GET_Y_LPARAM(lParam); if((::GetCapture() != pT->m_hWnd) && IsOverSplitterBar(xPos, yPos)) { m_xySplitterPosNew = m_xySplitterPos; pT->SetCapture(); m_hWndFocusSave = pT->SetFocus(); ::SetCursor(m_hCursor); if(!m_bFullDrag) DrawGhostBar(); if(m_bVertical) m_cxyDragOffset = xPos - m_rcSplitter.left - m_xySplitterPos; else m_cxyDragOffset = yPos - m_rcSplitter.top - m_xySplitterPos; } else if((::GetCapture() == pT->m_hWnd) && !IsOverSplitterBar(xPos, yPos)) { ::ReleaseCapture(); } bHandled = FALSE; return 1; } LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { T* pT = static_cast(this); if(::GetCapture() == pT->m_hWnd) { m_xySplitterPosNew = m_xySplitterPos; ::ReleaseCapture(); } bHandled = FALSE; return 1; } LRESULT OnLButtonDoubleClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { T* pT = static_cast(this); pT->SetSplitterPos(); // default return 0; } LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { if(!m_bFullDrag) DrawGhostBar(); if((m_xySplitterPosNew != -1) && (!m_bFullDrag || (m_xySplitterPos != m_xySplitterPosNew))) { m_xySplitterPos = m_xySplitterPosNew; m_xySplitterPosNew = -1; UpdateSplitterLayout(); T* pT = static_cast(this); pT->UpdateWindow(); } if(m_hWndFocusSave != NULL) ::SetFocus(m_hWndFocusSave); return 0; } LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) { T* pT = static_cast(this); if(::GetCapture() == pT->m_hWnd) { switch(wParam) { case VK_RETURN: m_xySplitterPosNew = m_xySplitterPos; // FALLTHROUGH case VK_ESCAPE: ::ReleaseCapture(); break; case VK_LEFT: case VK_RIGHT: if(m_bVertical) { POINT pt = {}; ::GetCursorPos(&pt); int xyPos = m_xySplitterPos + ((wParam == VK_LEFT) ? -pT->m_cxyStep : pT->m_cxyStep); int cxyMax = m_rcSplitter.right - m_rcSplitter.left; if(xyPos < (m_cxyMin + m_cxyBarEdge)) xyPos = m_cxyMin; else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin)) xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin; pt.x += xyPos - m_xySplitterPos; ::SetCursorPos(pt.x, pt.y); } break; case VK_UP: case VK_DOWN: if(!m_bVertical) { POINT pt = {}; ::GetCursorPos(&pt); int xyPos = m_xySplitterPos + ((wParam == VK_UP) ? -pT->m_cxyStep : pT->m_cxyStep); int cxyMax = m_rcSplitter.bottom - m_rcSplitter.top; if(xyPos < (m_cxyMin + m_cxyBarEdge)) xyPos = m_cxyMin; else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin)) xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin; pt.y += xyPos - m_xySplitterPos; ::SetCursorPos(pt.x, pt.y); } break; default: break; } } else { bHandled = FALSE; } return 0; } LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM, BOOL& bHandled) { T* pT = static_cast(this); if(::GetCapture() != pT->m_hWnd) { if(m_nSinglePane == SPLIT_PANE_NONE) { if((m_nDefActivePane == SPLIT_PANE_LEFT) || (m_nDefActivePane == SPLIT_PANE_RIGHT)) ::SetFocus(m_hWndPane[m_nDefActivePane]); } else { ::SetFocus(m_hWndPane[m_nSinglePane]); } } bHandled = FALSE; return 1; } LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { T* pT = static_cast(this); LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam); if((lRet == MA_ACTIVATE) || (lRet == MA_ACTIVATEANDEAT)) { DWORD dwPos = ::GetMessagePos(); POINT pt = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) }; pT->ScreenToClient(&pt); RECT rcPane = {}; for(int nPane = 0; nPane < m_nPanesCount; nPane++) { if(GetSplitterPaneRect(nPane, &rcPane) && (::PtInRect(&rcPane, pt) != FALSE)) { m_nDefActivePane = nPane; break; } } } return lRet; } LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { T* pT = static_cast(this); pT->GetSystemSettings(true); return 0; } // Implementation - internal helpers void Init() { m_hCursor = ::LoadCursor(NULL, m_bVertical ? IDC_SIZEWE : IDC_SIZENS); T* pT = static_cast(this); pT->GetSystemSettings(false); } void UpdateSplitterLayout() { if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1)) return; T* pT = static_cast(this); RECT rect = {}; if(m_nSinglePane == SPLIT_PANE_NONE) { if(GetSplitterBarRect(&rect)) pT->InvalidateRect(&rect); for(int nPane = 0; nPane < m_nPanesCount; nPane++) { if(GetSplitterPaneRect(nPane, &rect)) { if(m_hWndPane[nPane] != NULL) ::SetWindowPos(m_hWndPane[nPane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); else pT->InvalidateRect(&rect); } } } else { if(GetSplitterPaneRect(m_nSinglePane, &rect)) { if(m_hWndPane[m_nSinglePane] != NULL) ::SetWindowPos(m_hWndPane[m_nSinglePane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); else pT->InvalidateRect(&rect); } } } bool GetSplitterBarRect(LPRECT lpRect) const { ATLASSERT(lpRect != NULL); if((m_nSinglePane != SPLIT_PANE_NONE) || (m_xySplitterPos == -1)) return false; if(m_bVertical) { lpRect->left = m_rcSplitter.left + m_xySplitterPos; lpRect->top = m_rcSplitter.top; lpRect->right = m_rcSplitter.left + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; lpRect->bottom = m_rcSplitter.bottom; } else { lpRect->left = m_rcSplitter.left; lpRect->top = m_rcSplitter.top + m_xySplitterPos; lpRect->right = m_rcSplitter.right; lpRect->bottom = m_rcSplitter.top + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; } return true; } bool GetSplitterPaneRect(int nPane, LPRECT lpRect) const { ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT)); ATLASSERT(lpRect != NULL); bool bRet = true; if(m_nSinglePane != SPLIT_PANE_NONE) { if(nPane == m_nSinglePane) *lpRect = m_rcSplitter; else bRet = false; } else if(nPane == SPLIT_PANE_LEFT) { if(m_bVertical) { lpRect->left = m_rcSplitter.left; lpRect->top = m_rcSplitter.top; lpRect->right = m_rcSplitter.left + m_xySplitterPos; lpRect->bottom = m_rcSplitter.bottom; } else { lpRect->left = m_rcSplitter.left; lpRect->top = m_rcSplitter.top; lpRect->right = m_rcSplitter.right; lpRect->bottom = m_rcSplitter.top + m_xySplitterPos; } } else if(nPane == SPLIT_PANE_RIGHT) { if(m_bVertical) { lpRect->left = m_rcSplitter.left + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; lpRect->top = m_rcSplitter.top; lpRect->right = m_rcSplitter.right; lpRect->bottom = m_rcSplitter.bottom; } else { lpRect->left = m_rcSplitter.left; lpRect->top = m_rcSplitter.top + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge; lpRect->right = m_rcSplitter.right; lpRect->bottom = m_rcSplitter.bottom; } } else { bRet = false; } return bRet; } bool IsOverSplitterRect(int x, int y) const { // -1 == don't check return (((x == -1) || ((x >= m_rcSplitter.left) && (x <= m_rcSplitter.right))) && ((y == -1) || ((y >= m_rcSplitter.top) && (y <= m_rcSplitter.bottom)))); } bool IsOverSplitterBar(int x, int y) const { if(m_nSinglePane != SPLIT_PANE_NONE) return false; if((m_xySplitterPos == -1) || !IsOverSplitterRect(x, y)) return false; int xy = m_bVertical ? x : y; int xyOff = m_bVertical ? m_rcSplitter.left : m_rcSplitter.top; return ((xy >= (xyOff + m_xySplitterPos)) && (xy < (xyOff + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge))); } void DrawGhostBar() { RECT rect = {}; if(GetSplitterBarRect(&rect)) { // convert client to window coordinates T* pT = static_cast(this); RECT rcWnd = {}; pT->GetWindowRect(&rcWnd); ::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rcWnd, 2); ::OffsetRect(&rect, -rcWnd.left, -rcWnd.top); // invert the brush pattern (looks just like frame window sizing) CBrush brush(CDCHandle::GetHalftoneBrush()); if(brush.m_hBrush != NULL) { CWindowDC dc(pT->m_hWnd); CBrushHandle brushOld = dc.SelectBrush(brush); dc.PatBlt(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, PATINVERT); dc.SelectBrush(brushOld); } } } void GetSystemSettings(bool bUpdate) { if((m_dwExtendedStyle & SPLIT_FIXEDBARSIZE) == 0) { m_cxySplitBar = ::GetSystemMetrics(m_bVertical ? SM_CXSIZEFRAME : SM_CYSIZEFRAME); } T* pT = static_cast(this); if((pT->GetExStyle() & WS_EX_CLIENTEDGE) != 0) { m_cxyBarEdge = 2 * ::GetSystemMetrics(m_bVertical ? SM_CXEDGE : SM_CYEDGE); m_cxyMin = 0; } else { m_cxyBarEdge = 0; m_cxyMin = 2 * ::GetSystemMetrics(m_bVertical ? SM_CXEDGE : SM_CYEDGE); } ::SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, &m_bFullDrag, 0); if(bUpdate) UpdateSplitterLayout(); } bool IsProportional() const { return ((m_dwExtendedStyle & SPLIT_PROPORTIONAL) != 0); } void StoreProportionalPos() { int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); if(cxyTotal > 0) m_nProportionalPos = ::MulDiv(m_xySplitterPos, m_nPropMax, cxyTotal); else m_nProportionalPos = 0; ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::StoreProportionalPos - %i\n"), m_nProportionalPos); } void UpdateProportionalPos() { int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); if(cxyTotal > 0) { int xyNewPos = ::MulDiv(m_nProportionalPos, cxyTotal, m_nPropMax); m_bUpdateProportionalPos = false; T* pT = static_cast(this); pT->SetSplitterPos(xyNewPos, false); } } bool IsRightAligned() const { return ((m_dwExtendedStyle & SPLIT_RIGHTALIGNED) != 0); } void StoreRightAlignPos() { int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); if(cxyTotal > 0) m_nProportionalPos = cxyTotal - m_xySplitterPos; else m_nProportionalPos = 0; ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::StoreRightAlignPos - %i\n"), m_nProportionalPos); } void UpdateRightAlignPos() { int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge); if(cxyTotal > 0) { m_bUpdateProportionalPos = false; T* pT = static_cast(this); pT->SetSplitterPos(cxyTotal - m_nProportionalPos, false); } } bool IsInteractive() const { return ((m_dwExtendedStyle & SPLIT_NONINTERACTIVE) == 0); } }; /////////////////////////////////////////////////////////////////////////////// // CSplitterWindowImpl - Implements a splitter window template class ATL_NO_VTABLE CSplitterWindowImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CSplitterImpl< T > { public: DECLARE_WND_CLASS_EX2(NULL, T, CS_DBLCLKS, COLOR_WINDOW) CSplitterWindowImpl(bool bVertical = true) : CSplitterImpl< T >(bVertical) { } BOOL SubclassWindow(HWND hWnd) { BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd); if(bRet != FALSE) { T* pT = static_cast(this); pT->Init(); this->SetSplitterRect(); } return bRet; } BEGIN_MSG_MAP(CSplitterWindowImpl) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground) MESSAGE_HANDLER(WM_SIZE, OnSize) CHAIN_MSG_MAP(CSplitterImpl< T >) FORWARD_NOTIFICATIONS() END_MSG_MAP() LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { // handled, no background painting needed return 1; } LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled) { if(wParam != SIZE_MINIMIZED) this->SetSplitterRect(); bHandled = FALSE; return 1; } }; /////////////////////////////////////////////////////////////////////////////// // CSplitterWindow/CHorSplitterWindow - Implements splitter windows to be used as is template class CSplitterWindowT : public CSplitterWindowImpl > { public: DECLARE_WND_CLASS_EX2(_T("WTL_SplitterWindow"), CSplitterWindowT, CS_DBLCLKS, COLOR_WINDOW) CSplitterWindowT() : CSplitterWindowImpl >(t_bVertical) { } }; typedef CSplitterWindowT CSplitterWindow; typedef CSplitterWindowT CHorSplitterWindow; } // namespace WTL #endif // __ATLSPLIT_H__