|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
See How the demo was built further down in this article. IntroductionI'd like to share my first attempt at adding tool windows like the ones you can find in applications like MS Visio. So here is the implementation of a snappable tool window (snaps to the sides of a view window) that also provides auto-hide and pinning features, see the picture above. Thanks to Bjarke Viksoe for his docking framework. Although this code is written from scratch, his framework got me started. See atldock.h. Reference
Additional flags and masks to manage the
The structure
Implementation detailsThe following custom Windows messages are defined for the framework: #ifndef SNAP_MSGBASE #define SNAP_MSGBASE WM_USER+860 #endif #define WM_SNAP_FLOAT SNAP_MSGBASE #define WM_SNAP_SNAP SNAP_MSGBASE + 1 #define WM_SNAP_HIDE SNAP_MSGBASE + 2 #define WM_SNAP_QUERYRECT SNAP_MSGBASE + 3 #define WM_SNAP_MOVEDONE SNAP_MSGBASE + 4 #define WM_SNAP_REPOSITION SNAP_MSGBASE + 5 #define WM_SNAP_UPDATELAYOUT SNAP_MSGBASE + 6 #define WM_SNAP_QUERYSIZE SNAP_MSGBASE + 7 One of the harder issues to solve was the dragging of windows between snapping positions and between snapped and floating state. After many failing attempts, I now manage the dragging of the windows myself. In doing so, I store the starting point of the cursor and the offset to a reference point on the window. The trick was to move the reference point and update the offset depending on which side the window is snapped to. E.g., when snapped to the bottom right corner, the lower right corner of the window is the reference point. When dragging the window to a floating position, it is important that the lower right corner stays in the same position independent of window state. If the window now is dragged and snapped to the upper left position, it is equally important to keep the upper left position of the window in the same position independent of state. I decided to keep the size of the internal window constant, the view that is. I thought that would be helpful in case the view is based on a dialog. Well, this decision didn't make moving any easier, since the outer size of the window now is changing depending on the snapping context or if the window is floating. Hence the elaborate reference point vs. offset for managing the dragging. The classes The template class // CSnapFloatingWindow and CSnapFloatingWindowImpl // typedef CWinTraits<WS_POPUPWINDOW|WS_CLIPSIBLINGS| WS_OVERLAPPED|WS_THICKFRAME|WS_DLGFRAME, WS_EX_TOOLWINDOW|WS_EX_WINDOWEDGE> CSnapFloatWinTraits; template<class T, class TBase=CWindow, class TWinTraits=CSnapFloatWinTraits> class ATL_NO_VTABLE CSnapFloatingWindowImpl : public CWindowImpl< T, TBase, TWinTraits >, public CSnapWindowMover<T> class CSnapFloatingWindow : public CSnapFloatingWindowImpl<CSnapFloatingWindow> The template class // CSnapAutoHideWindow and CSnapAutoHideWindowImpl // typedef CWinTraits<WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_THICKFRAME, WS_EX_WINDOWEDGE> CSnapAutoHideWinTraits; template<class T, class TBase=CWindow, class TWinTraits=CSnapAutoHideWinTraits> class ATL_NO_VTABLE CSnapAutoHideWindowImpl : public CWindowImpl< T, TBase, TWinTraits >, public CSnapWindowMover<T> // Timer id and interval used for auto hide enum { IDT_AUTOHIDE = 1234, IDT_INTERVAL = 500 }; class CSnapAutoHideWindow : public CSnapAutoHideWindowImpl<CSnapAutoHideWindow> The template class // CSnappingWindow and CSnappingWindowImpl // template<class T, class TSnappedWindow = CSnapAutoHideWindow, class TFloatingWindow = CSnapFloatingWindow, class TBase = CWindow, class TWinTraits = CControlWinTraits< class ATL_NO_VTABLE CSnappingWindowImpl : public CWindowImpl<T, TBase, TWinTraits> class CSnappingWindow : public CSnappingWindowImpl<CSnappingWindow< The floating and snapping window classes are created when a view is added to SNAPCONTEXT* AddSnappableWindow(HWND hWndView)
{
ATLASSERT( ::IsWindow(hWndView) );
if (!::IsWindow(hWndView))
return NULL;
// Initialize context
SNAPCONTEXT* pCtx = new SNAPCONTEXT;
::ZeroMemory(pCtx, sizeof(SNAPCONTEXT));
pCtx->hWndView = hWndView;
pCtx->hWndRoot = m_hWnd;
pCtx->dwFlags = snapHidden; // Is in hidden state
// Create snapping window
TSnappedWindow* wndSnapped = new TSnappedWindow(pCtx);
ATLASSERT(wndSnapped);
wndSnapped->Create(m_hWnd, rcDefault, NULL);
ATLASSERT(::IsWindow(wndSnapped->m_hWnd));
pCtx->hWndSnapped = *wndSnapped;
// Create floating window
TFloatingWindow* wndFloating = new TFloatingWindow(pCtx);
ATLASSERT(wndFloating);
TCHAR szCaption[128]; // max text length is 127 for floating caption
::GetWindowText(hWndView, szCaption, sizeof(szCaption)/sizeof(TCHAR));
wndFloating->Create(m_hWnd, rcDefault, szCaption);
ATLASSERT(::IsWindow(wndFloating->m_hWnd));
pCtx->hWndFloated = *wndFloating;
// Store context pointer in the container
// (used for lookup and for memory mgmnt)
m_snappableWindows.Add(pCtx);
return pCtx;
}
The default layout is calculated from the client window rectangle and taking scrollbars into consideration. See the image at the top of the article. void QueryRect(RECT& rect) { // Typically override this method to calculate your client layout T* pT = static_cast<T*>(this); HWND hWndClient = pT->GetClient(); ::GetWindowRect(hWndClient ,&rect); LONG style = ::GetWindowLong(hWndClient,GWL_STYLE); if (style & WS_VSCROLL) { rect.right -= ::GetSystemMetrics(SM_CXVSCROLL); } if (style & WS_HSCROLL) { rect.bottom -= ::GetSystemMetrics(SM_CYHSCROLL); } // Compensate for 3D edge of client window LONG styleEx = ::GetWindowLong(hWndClient,GWL_EXSTYLE); if (styleEx & WS_EX_CLIENTEDGE) ::InflateRect(&rect, -2, -2); } To doIn no particular order:
How the demo was builtRunning the WTL application wizard to generate a SDI application with an Edit view created the demo application. Following that, I added the wtlsnappable header file and a view window to be snapped. // //A dummy view // class CSnapView : public CWindowImpl<CSnapView> { public: DECLARE_WND_CLASS(NULL) BOOLPreTranslateMessage(MSG* pMsg) { pMsg; return FALSE; } BEGIN_MSG_MAP(CSnapView) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) END_MSG_MAP() LRESULT OnEraseBkgnd(UINT, WPARAM wParam, LPARAM, BOOL&) { HDC dc = (HDC)wParam; HBRUSH hBrush = ::CreateSolidBrush(RGB(0,128,0)); RECT rc; GetClientRect(&rc); ::FillRect(dc,&rc, hBrush); ::DeleteObject(hBrush); return 1; } };
// // MainFrm.h // // Include the snappable framework header file #include "wtlsnappable.h" class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler { public: // Window for the snappable window framework CSnappingWindow m_snapWindow; // Snappable views (normal WTL views) CSnapView m_view1,m_view2,m_view3,m_view4,m_view5; // ... WTL Wizard code omitted LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) { // Initiate snapping framework m_hWndClient = m_snapWindow.Create(m_hWnd, rcDefault, NULL, WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE); // ... Menu and toolbar setup omitted (see demo project) HWND hWndView = m_view.Create(m_hWndClient , rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_NOHIDESEL, WS_EX_CLIENTEDGE); m_snapWindow.SetClient(hWndView); // ... More wizard code omitted // Create and add the five views // Snapping window to a side will auto-hide them by default RECT rcView1 = {0,0,200,300}; RECT rcView2 = {0,0,200,200}; POINT ptFloat = {100,100}; m_view1.Create(m_hWnd, rcView1, _T("View 1"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view1); m_snapWindow.FloatWindow(m_view1,ptFloat); m_view2.Create(m_hWnd, rcView2, _T("View 2"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view2); m_snapWindow.SnapWindow(m_view2, snapTopLeft); m_view3.Create(m_hWnd, rcView1, _T("View 3"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view3); m_snapWindow.SnapWindow(m_view3, snapTop, 100); m_view4.Create(m_hWnd, rcView2, _T("View 4"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view4); // Forced view to stay open (will be pinned) m_snapWindow.SnapWindow(m_view4,snapBottomRight,0,snapPinned); m_view5.Create(m_hWnd, rcView2, _T("View 5"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view5); m_snapWindow.SnapWindow(m_view5, snapTop, 350); return 0; } // ... WTL Wizard code omitted }; That's all folks!
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||