Click here to Skip to main content
15,896,329 members
Articles / Desktop Programming / WTL

Snappable tool windows that auto-hide

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
20 Feb 20026 min read 103K   3.2K   43  
An article on the framework for implementing snapping windows.
#ifndef __WTL_SNAPPABLE_H_
#define __WTL_SNAPPABLE_H_

/////////////////////////////////////////////////////////////////////
// Tool windows that can either float or snap to the edges of another
// window. A snapped window can auto hide to show only it's title bar.
//
// The size of the view (child window being snapped or floated) is
// kept independent of snapping side or if floating.
//
// Written by Jens Nilsson, jnilsson@icebreaker.com
//
// The code and information is provided "as-is" without warranty
// of any kind, either expressed or implied.
//
//
// Thanks goes to Bjarke Viksoe for his WTL docking framwork which
// provided the inspiration to get me started.
// See http://www.viksoe.dk
/////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __cplusplus
   #error ATL requires C++ compilation (use a .cpp suffix)
#endif

#ifndef __ATLAPP_H__
   #error wtlsnappable.h requires atlapp.h to be included first
#endif

/////////////////////////////////////////////////////////////////////
// Context of the snappable window
//
struct SNAPCONTEXT
{
	HWND  hWndSnapped; // Snapped window
	HWND  hWndFloated; // Floating window
	HWND  hWndView;    // View
	HWND  hWndRoot;    // Snap window manager
	DWORD dwFlags;	   // Position and state flags
};

//
// Position combinations allowed are
//
// snapFloat
// snapLeft, snapLeft | snapTop, snapLeft | snapBottom
// snapRight, snapRight | snapTop, snapRight | snapBottom
// snapTop
// snapBottom
//
// snapPinned and snapMinibar has no effect when floating
//

//
// Enumeration which will enforce allowed position combination for the flags
//
enum SnapPosition
{
	snapFloat		= 0x00000000,
	snapLeft		= 0x00000001,
	snapTop			= 0x00000002,
	snapRight		= 0x00000004,
	snapBottom		= 0x00000008,
	// Convenience flags
	snapTopLeft		= 0x00000003, // snapLeft  | snapTop
	snapTopRight	= 0x00000006, // snapRight | snapTop
	snapBottomLeft	= 0x00000009, // snapLeft  | snapBottom
	snapBottomRight	= 0x0000000C, // snapRight | snapBottom
	// Hidden (unsnapped or unfloated)
	snapHidden		= 0x00000010,
};

//
// Additional flags and masking values to manage the dwFlags attribute
//
enum SnapFlags
{
	// State
	snapPinned		= 0x00000100,
	snapMinibar		= 0x00000200,

	// Masks
	snapPosition	= 0x000000FF,
	snapState		= 0x00FFFF00,
	snapReserved	= 0xFF000000,
};


/////////////////////////////////////////////////////////////////////
// Helper functions
inline bool IsFloating(DWORD dwFlags)
{
	return ((dwFlags & snapPosition) == snapFloat);
};
inline bool IsFloating(SNAPCONTEXT* pCtx)
{
	return IsFloating(pCtx->dwFlags);
};

inline bool IsHidden(DWORD dwFlags)
{
	return ((dwFlags & snapPosition) == snapHidden);
};
inline bool IsHidden(SNAPCONTEXT* pCtx)
{
	return IsHidden(pCtx->dwFlags);
};

inline bool HasVerticalCaption(DWORD dwFlags)
{
	return ((dwFlags & snapLeft)!=0 || (dwFlags & snapRight)!=0);
};
inline bool HasVerticalCaption(SNAPCONTEXT* pCtx)
{
	return HasVerticalCaption(pCtx->dwFlags);
};


/////////////////////////////////////////////////////////////////////
// Collection of snap context windows
//
typedef CSimpleValArray<SNAPCONTEXT *> CSnapContainer;


/////////////////////////////////////////////////////////////////////
// Information structure
//
struct SNAPWINDOWINFO
{
	SnapPosition	snapPosition;
	POINT			ptPosition;
	int				cxy;
	DWORD			dwFlags;
};

/////////////////////////////////////////////////////////////////////
// Default view windows style
//
#ifndef SNAP_DEFAULT_VIEW_STYLE
#define SNAP_DEFAULT_VIEW_STYLE WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS
#endif

/////////////////////////////////////////////////////////////////////
// Snappable framework custom messages
//
#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

/////////////////////////////////////////////////////////////////////
// Helper classes
//
class CSnapWindowInfo : public SNAPWINDOWINFO
{
public:
	CSnapWindowInfo()
	{
		memset(this, 0, sizeof(SNAPWINDOWINFO));
	}
};

// Offset reference point table depending on snap position
//
// SnapPosition			Window reference corner		Flags
// ------------------	-----------------------		------------------
// snapFloat			Top - Left					snapFloat
// snapLeft				Top - Left					snapLeft
// snapTop				Top - Left					snapTop
// snaptTopLeft			Top - Left					snapTop | snapLeft 
// snapTopRight			Top - Right					snapTop | snapRight
// snapRight			Top - Right					snapRight
// snapBottomRight		Bottom - Right				snapBottom | snapRight
// snapBottom			Bottom - Left				snapBottom
// snapBottomLeft		Bottom - Left				snapBottom | snapLeft
//

class CSnapTrackInfo
{
public:

	enum ReferencePosition
	{
		rpTopLeft		= 0,
		rpTopRight		= 1,
		rpBottomRight	= 2,
		rpBottomLeft	= 3,
	};

	static ReferencePosition ConvertFromSnapPosition(DWORD dwFlags)
	{
		switch (dwFlags & snapPosition)
		{
			case snapBottomRight:
				return rpBottomRight;
			case snapBottom:
			case snapBottomLeft:
				return rpBottomLeft;
			case snapTopRight:
			case snapRight:
				return rpTopRight;
		}
		return rpTopLeft;
	}

	CSnapTrackInfo()
	{
		m_refPos = rpTopLeft;
		m_offset.cx = 0;
		m_offset.cy = 0;
	}

	CSnapTrackInfo(const RECT& rcWin, const POINT& ptCursor, DWORD dwPos)
	{
		SetTrackInfo(rcWin, ptCursor, dwPos);
	}

	~CSnapTrackInfo() {}

	void SetTrackInfo(const RECT& rcWin, const POINT& ptCursor, DWORD dwPos)
	{
		m_size.cx = rcWin.right - rcWin.left;
		m_size.cy = rcWin.bottom - rcWin.top;

		m_refPos = ConvertFromSnapPosition(dwPos);

		m_offset.cx = ptCursor.x - rcWin.left;
		m_offset.cy = ptCursor.y - rcWin.top;

		if ( m_refPos == rpTopRight || m_refPos == rpBottomRight )
			m_offset.cx -= m_size.cx;
		if ( m_refPos == rpBottomRight || m_refPos == rpBottomLeft )
			m_offset.cy -= m_size.cy;
	}

	void MoveReferencePosition(const SIZE& size, DWORD dwTo)
	{
		ReferencePosition refPos = ConvertFromSnapPosition(dwTo);

		if ( m_refPos == refPos )
		{
			m_size = size;
			return;
		}

		if ( refPos == rpTopLeft)
			m_size = size;

		if ( m_refPos == rpTopRight || m_refPos == rpBottomRight )
			m_offset.cx += m_size.cx;
		if ( m_refPos == rpBottomRight || m_refPos == rpBottomLeft )
			m_offset.cy += m_size.cy;

		if ( refPos == rpTopRight || refPos == rpBottomRight )
			m_offset.cx -= m_size.cx;
		if ( refPos == rpBottomRight || refPos == rpBottomLeft )
			m_offset.cy -= m_size.cy;

		if ( refPos != rpTopLeft)
			m_size = size;

		m_refPos = refPos;
	}

	void GetReferencePoint(const POINT& ptCursor, POINT& ptRef)
	{
		ptRef.x = ptCursor.x - m_offset.cx;
		ptRef.y = ptCursor.y - m_offset.cy;
	}

	void GetRectangleFromReference(const POINT& ptRef, RECT& rc)
	{
		GetRectangleFromReference(ptRef, m_size, rc);
	}

	void GetRectangleFromReference(const POINT& ptRef, const SIZE& size, RECT& rc)
	{
		rc.left = ptRef.x;
		rc.top = ptRef.y;

		if ( m_refPos == rpTopRight || m_refPos == rpBottomRight )
			rc.left -= size.cx;
		if ( m_refPos == rpBottomRight || m_refPos == rpBottomLeft )
			rc.top -= size.cy;

		rc.right = rc.left + size.cx;
		rc.bottom = rc.top + size.cy;
	}

	void GetRectangle(const POINT& ptCursor, RECT& rc)
	{
		POINT ptRef;
		GetReferencePoint(ptCursor, ptRef);
		GetRectangleFromReference(ptRef, m_size, rc);
	}

	SIZE  m_size;
	SIZE  m_offset;
	ReferencePosition m_refPos;
};

/////////////////////////////////////////////////////////////////////
// CSnapWindowMover
//
// A helper class to move the snapped or docked window. Synchronizes the
// switch between floating and snapped position during the move operation.
//
// When window is within a specific distance from the layout border it will
// snap to the border. When draged out it will float again.
//
template <class T>
class ATL_NO_VTABLE CSnapWindowMover
{
public:
	// Member data
	SIZE  m_sizeDrag;			// The drag rectangle for snapping to the frame
	POINT m_ptStartCursorDrag;	// Cursor point at start of drag operation

	RECT  m_rcStartRect;		// Window rectangle at start of drag operation
	DWORD m_dwStartPos;			// Snap flags at start of drag operation

	//
	// Method used to manage dragging of snappable windows 
	//
	bool TrackMove(SNAPCONTEXT* pCtx)
	{
		T* pT = static_cast<T*>(this);

		// Store cursor starting point
		::GetCursorPos(&m_ptStartCursorDrag);

		// Don't start move until we are dragging the window
		if (! ::DragDetect(pT->m_hWnd, m_ptStartCursorDrag) )
			return false;

		CSnapTrackInfo snapTrackInfo;
		pT->StartTracking(pCtx, snapTrackInfo);

		// Get messages until capture lost or until Move is done
		while( pT->HasCapture(pCtx) )
		{
			MSG msg;
			if( !::GetMessage(&msg, NULL, 0, 0) ) 
			{
				::PostQuitMessage(msg.wParam);
				break;
			}

			switch( msg.message ) 
			{
				case WM_MOUSEMOVE:
					pT->OnMove(pCtx, msg.wParam, msg.pt, snapTrackInfo); 
					break;

				case WM_LBUTTONUP:
					pT->OnEndMove(pCtx); 
					pT->CancelTracking(pCtx);
					return true;

				case WM_RBUTTONDOWN:
					pT->CancelTracking(pCtx);
					return false;  

				case WM_KEYUP:
					pT->OnKey(pCtx, (int)msg.wParam, false);
					break;

				case WM_KEYDOWN:
					pT->OnKey(pCtx, (int)msg.wParam, true);
					if( msg.wParam == VK_ESCAPE ) 
					{
						pT->CancelTracking(pCtx);
						return false;
					}
					break;
	
				default:
					::DispatchMessage(&msg);
					break;
			}
		}

		ATLTRACE(_T("CSnapWindowMover::TrackMove - Lost capture\n"));

		pT->CancelTracking(pCtx);
		return false;
	}

	//
	// Helper methods
	//
	void StartTracking(SNAPCONTEXT* pCtx, CSnapTrackInfo& snapTrackInfo)
	{
		ATLTRACE(_T("CSnapWindowMover::StartTracking\n"));

		// Store starting data
		m_dwStartPos = pCtx->dwFlags;
		
		if ( IsFloating(pCtx->dwFlags) )
		{
			::SetCapture(pCtx->hWndFloated);
			::GetWindowRect(pCtx->hWndFloated, &m_rcStartRect);
		}
		else
		{
			::SetCapture(pCtx->hWndSnapped);
			::GetWindowRect(pCtx->hWndSnapped, &m_rcStartRect);
		}

		snapTrackInfo.SetTrackInfo(m_rcStartRect, m_ptStartCursorDrag, m_dwStartPos);

		// Set drag rectangle for snapping to layout
		m_sizeDrag.cx = ::GetSystemMetrics(SM_CXDRAG) * 6;
		m_sizeDrag.cy = ::GetSystemMetrics(SM_CYDRAG) * 6;
		return;
	}

	void CancelTracking(SNAPCONTEXT* pCtx)
	{
		if ( HasCapture(pCtx) )
			::ReleaseCapture();
		ATLTRACE(_T("CSnapWindowMover::CancelTracking\n"));
		return;
	}

	inline void GetLayoutRect(SNAPCONTEXT* pCtx, RECT* prc)
	{
		::SendMessage(pCtx->hWndRoot, WM_SNAP_QUERYRECT, 0, (LPARAM)prc);
		return;
	}

	// Check capture belongs to this context 
	bool HasCapture(SNAPCONTEXT* pCtx)
	{
		HWND hWnd = ::GetCapture();
		return (hWnd == pCtx->hWndFloated || hWnd == pCtx->hWndSnapped);
	}

	// Returns position part of snapping flags
	DWORD CheckSnapping(SNAPCONTEXT* pCtx, const RECT& rcLayout, const RECT& rect)
	{
		DWORD dwNewPos = (pCtx->dwFlags & snapPosition);

		// Window needs to be fully inside to snap to frame
		RECT rcTest = rcLayout;
		::InflateRect(&rcTest,m_sizeDrag.cx,m_sizeDrag.cy);
		RECT rcIntersect;
		::IntersectRect(&rcIntersect, &rcTest, &rect);
		if ( ::EqualRect(&rect, &rcIntersect) )
		{
			if ( (rect.left - rcLayout.left < m_sizeDrag.cx) && (rect.left - rcLayout.left > -m_sizeDrag.cx) )
				dwNewPos |= snapLeft;
			else
				dwNewPos &= ~snapLeft;

			if ( !(pCtx->dwFlags & snapLeft) && (rect.right - rcLayout.right < m_sizeDrag.cx && rect.right - rcLayout.right > -m_sizeDrag.cx) )
				dwNewPos |= snapRight;
			else
				dwNewPos &= ~snapRight;

			if ( (rect.top - rcLayout.top < m_sizeDrag.cy) && (rect.top - rcLayout.top > -m_sizeDrag.cy) )
				dwNewPos |= snapTop;
			else
				dwNewPos &= ~snapTop;

			if ( !(pCtx->dwFlags & snapTop) && (rect.bottom - rcLayout.bottom < m_sizeDrag.cy) && (rect.bottom - rcLayout.bottom > -m_sizeDrag.cy) )
				dwNewPos |= snapBottom;
			else
				dwNewPos &= ~snapBottom;
		}
		else
		{
				dwNewPos = snapFloat;
		}

		return dwNewPos;
	}

	void EnforceSnapPosition(SNAPCONTEXT* pCtx, const RECT& rcLayout, POINT& pos)
	{
		if (pCtx->dwFlags & snapLeft)
			pos.x = rcLayout.left;
		if (pCtx->dwFlags & snapTop)
			pos.y = rcLayout.top;
		if (pCtx->dwFlags & snapRight)
			pos.x = rcLayout.right;
		if (pCtx->dwFlags & snapBottom)
			pos.y = rcLayout.bottom;
	}

	bool FloatedReposition(SNAPCONTEXT* pCtx, const POINT& pt, DWORD dwNewPos, CSnapTrackInfo& snapTrackInfo)
	{
		if ( IsFloating(dwNewPos) )
		{
			POINT ptRef;
			snapTrackInfo.GetReferencePoint(pt, ptRef);
			::SetWindowPos(pCtx->hWndFloated, NULL, ptRef.x, ptRef.y, 0, 0, SWP_NOZORDER|SWP_NOSIZE);
			return true;
		}

		RECT rcView;
		::GetWindowRect(pCtx->hWndView, &rcView);
		SIZE size = {rcView.right - rcView.left,  rcView.bottom - rcView.top};

		::SendMessage(pCtx->hWndSnapped,WM_SNAP_QUERYSIZE, dwNewPos, (LPARAM)&size);

		snapTrackInfo.MoveReferencePosition(size, dwNewPos);

		CSnapWindowInfo swi;
		swi.snapPosition = (SnapPosition) dwNewPos;
		
		T* pT = static_cast<T*>(this);
		RECT rcLayout, rcSnap;
		pT->GetLayoutRect(pCtx, &rcLayout);
		
		snapTrackInfo.GetRectangle(pt, rcSnap);
		
		if ((dwNewPos & snapLeft) || (dwNewPos & snapRight))
			swi.cxy = rcSnap.top - rcLayout.top;
		else
			swi.cxy = rcSnap.left - rcLayout.left;
		
		::SendMessage(pCtx->hWndRoot, WM_SNAP_SNAP, (WPARAM)&swi, (LPARAM)pCtx);
		::SetCapture(pCtx->hWndSnapped);

		return false;
	}

	bool SnappedReposition(SNAPCONTEXT* pCtx, const POINT& pt, DWORD dwNewPos, CSnapTrackInfo& snapTrackInfo)
	{
		// Retrieve size of view
		RECT rcView;
		::GetWindowRect(pCtx->hWndView, &rcView);
		SIZE size = {rcView.right - rcView.left,  rcView.bottom - rcView.top};

		if ( ! IsFloating(dwNewPos) )
		{
			::SendMessage(pCtx->hWndSnapped,WM_SNAP_QUERYSIZE, dwNewPos, (LPARAM)&size);

			snapTrackInfo.MoveReferencePosition(size, dwNewPos);

			// Update position part of flag
			pCtx->dwFlags &= ~snapPosition;
			pCtx->dwFlags |= dwNewPos;


			T* pT = static_cast<T*>(this);
			RECT rcLayout;
			pT->GetLayoutRect(pCtx, &rcLayout);

			POINT ptRef;
			snapTrackInfo.GetReferencePoint(pt, ptRef);

			// Enforce snapped positioning  (moving window to the snapped edge)
			pT->EnforceSnapPosition(pCtx, rcLayout, ptRef);

			RECT rcSnap;
			snapTrackInfo.GetRectangleFromReference(ptRef, size, rcSnap);

			POINT pos = { rcSnap.left, rcSnap.top };

			// Pos should be relative parent client rectangle
			::ScreenToClient(::GetParent(pCtx->hWndSnapped), &pos);

			::SetWindowPos(pCtx->hWndSnapped, NULL, pos.x, pos.y, size.cx, size.cy, SWP_NOZORDER);
			::SendMessage(pCtx->hWndSnapped,WM_SNAP_UPDATELAYOUT,0,0);

			return true;
		}

		::SendMessage(pCtx->hWndFloated,WM_SNAP_QUERYSIZE, dwNewPos, (LPARAM)&size);

		snapTrackInfo.MoveReferencePosition(size, dwNewPos);

		CSnapWindowInfo swi;
		swi.snapPosition = (SnapPosition) dwNewPos;
		
		snapTrackInfo.GetReferencePoint(pt, swi.ptPosition);

		::SendMessage(pCtx->hWndRoot, WM_SNAP_FLOAT, (WPARAM)&swi, (LPARAM)pCtx);
		::SetCapture(pCtx->hWndFloated);

		return false;
	}

	// Screen coordinates
	void OnMove(SNAPCONTEXT* pCtx, DWORD dwMouseKeys, POINT& ptCursor, CSnapTrackInfo& snapTrackInfo)
	{
		T* pT = static_cast<T*>(this);

		RECT rcLayout;
		pT->GetLayoutRect(pCtx, &rcLayout);

		RECT rc;
		snapTrackInfo.GetRectangle(ptCursor, rc);

		DWORD dwNewPos = pT->CheckSnapping(pCtx, rcLayout, rc);

		if ( IsFloating(pCtx)  )
		{
			pT->FloatedReposition(pCtx, ptCursor, dwNewPos, snapTrackInfo);
		}
		else
		{
			pT->SnappedReposition(pCtx, ptCursor, dwNewPos, snapTrackInfo);
		}
	}

	void OnEndMove(SNAPCONTEXT* pCtx)
	{
		ATLTRACE(_T("CSnapWindowMover::OnEndMove\n"));
	}
	void OnKey(SNAPCONTEXT* pCtx, int nKeyCode, bool bKeyDown) {}

};

/////////////////////////////////////////////////////////////////////
// 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>
{
public:
	DECLARE_WND_CLASS_EX(NULL, CS_DBLCLKS, NULL)

	SNAPCONTEXT* m_pCtx;

	typedef CSnapFloatingWindowImpl< T , TBase, TWinTraits > thisClass;

	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
		MESSAGE_HANDLER(WM_SYSCOMMAND, OnSysCommand)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnNCLeftButtonDown)
		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingsChange)
		MESSAGE_HANDLER(WM_SNAP_QUERYSIZE, OnSnapQuerySize)
	END_MSG_MAP()

	CSnapFloatingWindowImpl(SNAPCONTEXT* pCtx) : m_pCtx(pCtx)
	{
		BOOL bHandled;
		OnSettingsChange(0,(WPARAM)0, (LPARAM)0, bHandled);
	}

	HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL,
				DWORD dwStyle = 0, DWORD dwExStyle = 0,
				UINT nID = 0, LPVOID lpCreateParam = NULL)
	{
		return CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, rcPos, szWindowName, dwStyle, dwExStyle, nID, lpCreateParam);
	}

	virtual void OnFinalMessage(HWND /*hWnd*/)
	{
		delete this;
	}

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		DefWindowProc();

		// Change system menu
		HMENU hSysMenu = GetSystemMenu(FALSE);
		if (::IsMenu(hSysMenu))
		{
			int count = ::GetMenuItemCount(hSysMenu);
			while (count > 0)
			{
				UINT iPos = --count;
				UINT wID = ::GetMenuItemID(hSysMenu, iPos);
				if ( wID == SC_CLOSE )
				{
					MENUITEMINFO mii;
					mii.cbSize = sizeof(MENUITEMINFO);
					mii.fMask = MIIM_TYPE;
					mii.fType = MFT_STRING;
					mii.dwTypeData = _T("&Hide\tAlt+F4");
					mii.cch = 12;
#if(WINVER >= 0x0500)
					mii.fMask |= MIIM_BITMAP;
					mii.hbmpItem = HBMMENU_MBAR_CLOSE;
#endif
					::SetMenuItemInfo(hSysMenu, iPos, TRUE, &mii);
				}
				else if ( wID != SC_SIZE && wID != SC_MOVE)
				{
					::RemoveMenu(hSysMenu, iPos, MF_BYPOSITION);
				}
			}
		}
		return 0;
	}

	LRESULT OnSettingsChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		bHandled = FALSE; //Give other handlers a chance
		return 0;
	}

	LRESULT OnSysCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		switch( wParam & 0xFFF0 )
		{
			case SC_CLOSE: 
				::SendMessage(m_pCtx->hWndRoot, WM_SNAP_HIDE, 0, (LPARAM)m_pCtx);
				return 0;
		}
		DefWindowProc();
		return 0;
	}

	LRESULT OnNCLeftButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		SetFocus();
		if( wParam==HTCAPTION ) 
		{
			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			T* pT = static_cast<T*>(this);
			pT->TrackMove(m_pCtx);
			if (!IsFloating(m_pCtx))
				::SendMessage(m_pCtx->hWndSnapped,WM_SNAP_MOVEDONE,0,0);
			return 0;
		}
		bHandled = FALSE;
		return 0;
	}

	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 1; // handled, no background painting needed
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if ( ! IsFloating(m_pCtx) )
			return 1;

		if (::IsWindow(m_pCtx->hWndView))
		{
			RECT rc;
			GetClientRect(&rc);
			::SetWindowPos(m_pCtx->hWndView, NULL, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER | SWP_NOACTIVATE);
		}
		return 0;
	}

	LRESULT OnSnapQuerySize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		LPSIZE lpSize = (LPSIZE) lParam;
		RECT rcFloat = { 0, 0, lpSize->cx, lpSize->cy };

		if ( IsFloating(wParam) )
		{
			// Adjust the size for floating window to keep the size of the view
			::AdjustWindowRectEx(&rcFloat, GetWndStyle(0), FALSE, GetWndExStyle(0));
			lpSize->cx = rcFloat.right - rcFloat.left;
			lpSize->cy = rcFloat.bottom - rcFloat.top;
		}
		else
		{
			ATLASSERT(FALSE);
			// Should be a floating window
			return FALSE;
		}

		return TRUE;
	}
};

class CSnapFloatingWindow : public CSnapFloatingWindowImpl<CSnapFloatingWindow>
{
public:
	DECLARE_WND_CLASS_EX(_T("WTL_SnapFloatingWindow"), CS_DBLCLKS, NULL)

	CSnapFloatingWindow(SNAPCONTEXT* pCtx) : CSnapFloatingWindowImpl<CSnapFloatingWindow>(pCtx)
	{ 
	}
};



/////////////////////////////////////////////////////////////////////
// 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> 
{
public:
	DECLARE_WND_CLASS_EX(NULL, CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, NULL)

	// Timer id and interval used for auto hide
	enum { IDT_AUTOHIDE = 1234, IDT_INTERVAL = 500 };

	// These could probably be static
	CFont m_hFontSmCaption;	// Horizontal font
	CFont m_vFontSmCaption; // Vertical font
	SIZE  m_sizeSmCapBtn;	// Size of caption buttons
	int   m_cxyCaption;		// Height of caption bar
	int   m_cxyBorder;		// Width of border

	SNAPCONTEXT* m_pCtx;

	SIZE  m_size; // Size of expanded window

	// State during mouse button down / up
	bool  m_bCloseDown, m_bPushpinDown;

	// These are set by UpdateLayout
	RECT m_rcChild;
	RECT m_rcCaption;
	RECT m_rcCloseBtn;
	RECT m_rcPushpinBtn;

	typedef CSnapAutoHideWindowImpl< T , TBase, TWinTraits > thisClass;

	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingsChange)
		MESSAGE_HANDLER(WM_SYSCOMMAND, OnSysCommand)
		MESSAGE_HANDLER(WM_NCHITTEST, OnNCHitTest)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLeftButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONUP, OnLeftButtonUp)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
		MESSAGE_HANDLER(WM_MOUSEHOVER, OnMouseHover)
		MESSAGE_HANDLER(WM_TIMER, OnTimer)
		MESSAGE_HANDLER(WM_SNAP_MOVEDONE, OnSnapMoveDone)
		MESSAGE_HANDLER(WM_SNAP_REPOSITION, OnSnapReposition)
		MESSAGE_HANDLER(WM_SNAP_UPDATELAYOUT, OnSnapUpdateLayout)
		MESSAGE_HANDLER(WM_SNAP_QUERYSIZE, OnSnapQuerySize)
	END_MSG_MAP()

	CSnapAutoHideWindowImpl(SNAPCONTEXT* pCtx) : m_pCtx(pCtx)
	{
		m_bCloseDown = m_bPushpinDown = false;
		BOOL bHandled;
		OnSettingsChange(0,(WPARAM)0, (LPARAM)0, bHandled);
	}

	HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL,
				DWORD dwStyle = 0, DWORD dwExStyle = 0,
				UINT nID = 0, LPVOID lpCreateParam = NULL)
	{
		return CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, rcPos, szWindowName, dwStyle, dwExStyle, nID, lpCreateParam);
	}

	virtual void OnFinalMessage(HWND /*hWnd*/)
	{
		delete this;
	}

	// Message handlers

	LRESULT OnSettingsChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		// Height of caption bar
		m_cxyCaption =  ::GetSystemMetrics(SM_CYSMCAPTION);

		// Width of border
		m_cxyBorder = ::GetSystemMetrics(SM_CXFRAME);

		// Small caption button
		m_sizeSmCapBtn.cx = ::GetSystemMetrics(SM_CXSMSIZE) - 2; // Even smaller
		m_sizeSmCapBtn.cy = ::GetSystemMetrics(SM_CYSMSIZE) - 4; // Even smaller

		// Caption font
		NONCLIENTMETRICS ncm = { 0 };
		ncm.cbSize = sizeof(ncm);
		::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
		if( !m_hFontSmCaption.IsNull() )
			m_hFontSmCaption.DeleteObject();
		// Don't use bold font
		ncm.lfSmCaptionFont.lfWeight = FW_NORMAL;
		m_hFontSmCaption.CreateFontIndirect(&ncm.lfSmCaptionFont); //Small caption font

		if( !m_vFontSmCaption.IsNull() )
			m_vFontSmCaption.DeleteObject();
		ncm.lfSmCaptionFont.lfEscapement = ncm.lfSmCaptionFont.lfOrientation  = 2700;
		m_vFontSmCaption.CreateFontIndirect(&ncm.lfSmCaptionFont); //Small caption font

		bHandled = FALSE; //Give other handlers a chance
		return 0;
	}

	LRESULT OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		LRESULT lRet = DefWindowProc(uMsg, wParam, lParam);

		if (m_pCtx->dwFlags & snapMinibar)
		{
			// If auto hidden as a mini bar, no sizing at all
			switch (lRet)
			{
				case HTLEFT:
				case HTTOP:
				case HTRIGHT:
				case HTBOTTOM:
				case HTTOPLEFT:
				case HTTOPRIGHT:
				case HTBOTTOMLEFT:
				case HTBORDER:
					return HTCLIENT;
				default:
					break;
			}
		}

		// Prohibit sizing of snapped side
		DWORD dwPos = (m_pCtx->dwFlags & snapPosition);

		switch (lRet)
		{
			case HTLEFT:
				if (dwPos & snapLeft)
					lRet = HTBORDER;
				break;

			case HTTOP:
				if (dwPos & snapTop)
					lRet = HTBORDER;
				break;

			case HTRIGHT:
				if (dwPos & snapRight)
					lRet = HTBORDER;
				break;

			case HTBOTTOM:
				if (dwPos & snapBottom)
					lRet = HTBORDER;
				break;

			case HTTOPLEFT:
				if (dwPos == (snapTop | snapLeft) )
					lRet = HTBORDER;
				else if (dwPos & snapTop)
					lRet = HTLEFT;
				else if (dwPos & snapLeft)
					lRet = HTTOP;
				break;

			case HTTOPRIGHT:
				if (dwPos == (snapTop | snapRight) )
					lRet = HTBORDER;
				else if (dwPos & snapTop)
					lRet = HTRIGHT;
				else if (dwPos & snapRight)
					lRet = HTTOP;
				break;

			case HTBOTTOMLEFT:
				if (dwPos == (snapBottom | snapLeft) )
					lRet = HTBORDER;
				else if (dwPos & snapBottom)
					lRet = HTLEFT;
				else if (dwPos & snapLeft)
					lRet = HTBOTTOM;
				break;

			case HTBOTTOMRIGHT:
				if (dwPos == (snapBottom | snapRight) )
					lRet = HTBORDER;
				else if (dwPos & snapBottom)
					lRet = HTRIGHT;
				else if (dwPos & snapRight)
					lRet = HTBOTTOM;
				break;
		}
		return lRet;
	}

	LRESULT OnSysCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		switch( wParam & 0xFFF0 )
		{
			case SC_CLOSE: 
				::SendMessage(m_pCtx->hWndRoot, WM_SNAP_HIDE, 0, (LPARAM)m_pCtx);
				return 0;
		}
		DefWindowProc();
		return 0;
	}


	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 1; // handled, no background painting needed
	}


	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		CPaintDC dc(m_hWnd);
		RECT rc;
		GetClientRect(&rc);

		dc.ExcludeClipRect(&m_rcChild);
		dc.IntersectClipRect(&rc);
		dc.FillRect(&rc, (HBRUSH)LongToPtr(COLOR_3DFACE + 1));

		bool bVertical = HasVerticalCaption(m_pCtx);

		T* pT = static_cast<T*>(this);
		pT->DrawCaption(dc.m_hDC, bVertical, m_rcCaption);

		return 0;
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if ( IsFloating(m_pCtx) || IsHidden(m_pCtx) )
			return 1;

		if (::IsWindow(m_pCtx->hWndView))
		{
			T* pT = static_cast<T*>(this);
			pT->UpdateLayout();
			::SetWindowPos(m_pCtx->hWndView, NULL, m_rcChild.left, m_rcChild.top, m_rcChild.right-m_rcChild.left, m_rcChild.bottom-m_rcChild.top, SWP_NOZORDER|SWP_NOACTIVATE);
		}
		return 0;
	}

	LRESULT OnLeftButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
		if (::PtInRect(&m_rcCloseBtn,pt))
		{
			m_bCloseDown = true;
			InvalidateRect(&m_rcCloseBtn,FALSE);
			return 1;
		}
		if (::PtInRect(&m_rcPushpinBtn,pt))
		{
			m_bPushpinDown = true;
			InvalidateRect(&m_rcPushpinBtn,FALSE);
			return 2;
		}

		m_bCloseDown = m_bPushpinDown = false;

		if (m_pCtx->dwFlags & snapMinibar)
			return 3;

		// Kill timer during Move operation
		T* pT = static_cast<T*>(this);
		::KillTimer(m_hWnd, pT->IDT_AUTOHIDE);

		ClientToScreen(&pt);
		pT->TrackMove(m_pCtx);

		// If the timer is needed it will be set here
		pT->CheckAutoHide();

		SetWindowPos(HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);

		return 0;
	}

	LRESULT OnLeftButtonUp(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
		if (m_bCloseDown && ::PtInRect(&m_rcCloseBtn,pt))
		{
			m_bPushpinDown = m_bCloseDown = false;
			ShowWindow(SW_HIDE);
			return 0;
		}
		else if (m_bPushpinDown && ::PtInRect(&m_rcPushpinBtn,pt))
		{
			if (m_pCtx->dwFlags & snapPinned)
			{
				m_pCtx->dwFlags &= ~snapPinned;
			}
			else
			{
				m_pCtx->dwFlags |= snapPinned;
			}
			m_bPushpinDown = false;
			InvalidateRect(&m_rcPushpinBtn, FALSE);
		}

		m_bPushpinDown = m_bCloseDown = false;

		T* pT = static_cast<T*>(this);
		if (m_pCtx->dwFlags & snapMinibar)
		{
			pT->ExpandWindow();
		}
		// Will set timer if needed
		pT->CheckAutoHide();

		return 0;
	}

	LRESULT OnSnapReposition(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		RECT* pRect = reinterpret_cast<RECT*>(lParam);
		RECT rc;
		GetWindowRect(&rc);
		POINT pt = { rc.left, rc.top };
		if (m_pCtx->dwFlags & snapLeft)
			pt.x = pRect->left;
		if (m_pCtx->dwFlags & snapTop)
			pt.y = pRect->top;
		if (m_pCtx->dwFlags & snapRight)
			pt.x = pRect->right - rc.right + rc.left;
		if (m_pCtx->dwFlags & snapBottom)
			pt.y = pRect->bottom - rc.bottom + rc.top;
		::ScreenToClient(GetParent(), (LPPOINT)&pt);
		::SetWindowPos(m_hWnd,NULL,pt.x,pt.y,0,0,SWP_NOSIZE|SWP_NOACTIVATE|SWP_NOZORDER);

		T* pT = static_cast<T*>(this);
		pT->UpdateLayout();
		return 0;
	}

	LRESULT OnSnapMoveDone(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		// Will set timer if needed
		pT->CheckAutoHide();
		return 0;
	}

	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		if ((m_pCtx->dwFlags & snapPosition) == snapFloat)
			return 1;

		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

		if (wParam & MK_LBUTTON )
		{
			bool bCloseDown = (::PtInRect(&m_rcCloseBtn,pt) == TRUE);
			bool bPushpinDown = (::PtInRect(&m_rcPushpinBtn,pt) == TRUE);

			if (m_bCloseDown != bCloseDown)
				InvalidateRect(&m_rcCloseBtn, FALSE);
			m_bCloseDown = bCloseDown;

			if (m_bPushpinDown != bPushpinDown)
				InvalidateRect(&m_rcPushpinBtn, FALSE);
			m_bPushpinDown = bPushpinDown;

			return 1;
		}

		if (m_pCtx->dwFlags & snapMinibar)
		{
			TRACKMOUSEEVENT tme = { 0 };
			tme.cbSize = sizeof(tme);
			tme.hwndTrack = m_hWnd;
			tme.dwFlags = TME_HOVER;
			tme.dwHoverTime = HOVER_DEFAULT;
			_TrackMouseEvent(&tme);
		}
		else
		{
			if (::PtInRect(&m_rcCloseBtn,pt) == FALSE && ::PtInRect(&m_rcPushpinBtn,pt) == FALSE)
				::SetCursor(::LoadCursor(NULL, IDC_SIZEALL));
		}
		return 0;
	}

	LRESULT OnSnapQuerySize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		LPSIZE lpSize = (LPSIZE) lParam;
		RECT rcSnap = { 0, 0, lpSize->cx, lpSize->cy };

		// Adjust the size for snapping window to keep the size of the view
		::AdjustWindowRectEx(&rcSnap, GetWndStyle(0), FALSE, GetWndExStyle(0));
		lpSize->cx = rcSnap.right - rcSnap.left;
		lpSize->cy = rcSnap.bottom - rcSnap.top;

		// Add size for caption
		if ((wParam & snapLeft) || (wParam & snapRight))
		{	
			lpSize->cx += m_cxyCaption;
		}
		else if ((wParam & snapTop) || (wParam & snapBottom))
		{	
			lpSize->cy += m_cxyCaption;
		}
		else
		{
			ATLASSERT(FALSE);
			// Should be snapped to at least one side
			return FALSE;
		}

		return TRUE;
	}

	LRESULT OnSnapUpdateLayout(UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->UpdateLayout();
		::SetWindowPos(m_pCtx->hWndView, NULL, m_rcChild.left, m_rcChild.top, m_rcChild.right-m_rcChild.left, m_rcChild.bottom-m_rcChild.top, SWP_NOZORDER | SWP_NOACTIVATE);

		// Extra flags
		if (wParam & snapPinned)
			m_pCtx->dwFlags |= snapPinned;

		if ( (wParam & snapMinibar) && (m_pCtx->dwFlags & snapPinned) == 0 )
			pT->ShrinkWindow();
		else
			InvalidateRect(&m_rcCaption, TRUE);

		return 0;
	}

	LRESULT OnMouseHover(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if (wParam & MK_LBUTTON)
			return 0;

		T* pT = static_cast<T*>(this);
		pT->ExpandWindow();
		// Will set timer if needed
		pT->CheckAutoHide();
		return 0;
	}

	LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		T* pT = static_cast<T*>(this);
		if ( wParam == pT->IDT_AUTOHIDE)
		{
			POINT ptCursor;
			GetCursorPos(&ptCursor);
			RECT rcWindow;
			GetWindowRect(&rcWindow);

			// Make little bigger so we are not accidently hiding the window
			::InflateRect(&rcWindow, ::GetSystemMetrics(SM_CXDRAG) * 6, ::GetSystemMetrics(SM_CYDRAG) * 6);

			if (! ::PtInRect(&rcWindow, ptCursor) )
			{
				::KillTimer(m_hWnd, pT->IDT_AUTOHIDE);
				pT->ShrinkWindow();
			}
			return 0;
		}

		bHandled = FALSE;
		return 1;
	}

	//
	// Overridable methods
	//

	void UpdateLayout()
	{
		// Updates child and caption rectangles
		GetClientRect(&m_rcChild);
		m_rcCaption = m_rcChild;

		const dxy = (m_cxyCaption - m_sizeSmCapBtn.cx) / 2;

		if (m_pCtx->dwFlags & snapLeft)
		{	
			m_rcChild.right -= m_cxyCaption;
			m_rcCaption.left = m_rcChild.right;
			::SetRect(&m_rcCloseBtn,m_rcCaption.left+dxy,m_rcCaption.top+2,m_rcCaption.left+dxy+m_sizeSmCapBtn.cx, m_rcCaption.top+2+m_sizeSmCapBtn.cy);
			::SetRect(&m_rcPushpinBtn,m_rcCloseBtn.left,m_rcCloseBtn.bottom+2,m_rcCloseBtn.right, m_rcCloseBtn.bottom+2+m_sizeSmCapBtn.cy);
		}
		else if (m_pCtx->dwFlags & snapRight)
		{	
			m_rcChild.left += m_cxyCaption;
			m_rcCaption.right = m_rcChild.left;
			::SetRect(&m_rcCloseBtn,m_rcCaption.left+dxy,m_rcCaption.top+2,m_rcCaption.left+dxy+m_sizeSmCapBtn.cx, m_rcCaption.top+2+m_sizeSmCapBtn.cy);
			::SetRect(&m_rcPushpinBtn,m_rcCloseBtn.left,m_rcCloseBtn.bottom+2,m_rcCloseBtn.right, m_rcCloseBtn.bottom+2+m_sizeSmCapBtn.cy);
		}
		else if (m_pCtx->dwFlags & snapTop)
		{	
			m_rcChild.bottom -= m_cxyCaption;
			m_rcCaption.top = m_rcChild.bottom;
			::SetRect(&m_rcCloseBtn,m_rcCaption.left+2,m_rcCaption.top + dxy,m_rcCaption.left+2+m_sizeSmCapBtn.cx, m_rcCaption.top+dxy+m_sizeSmCapBtn.cy);
			::SetRect(&m_rcPushpinBtn,m_rcCloseBtn.right+2,m_rcCloseBtn.top,m_rcCloseBtn.right+2+m_sizeSmCapBtn.cx, m_rcCloseBtn.bottom);
		}
		else if (m_pCtx->dwFlags & snapBottom)
		{	
			m_rcChild.top += m_cxyCaption;
			m_rcCaption.bottom = m_rcChild.top;
			::SetRect(&m_rcCloseBtn,m_rcCaption.left+2,m_rcCaption.top + dxy,m_rcCaption.left+2+m_sizeSmCapBtn.cx, m_rcCaption.top+dxy+m_sizeSmCapBtn.cy);
			::SetRect(&m_rcPushpinBtn,m_rcCloseBtn.right+2,m_rcCloseBtn.top,m_rcCloseBtn.right+2+m_sizeSmCapBtn.cx, m_rcCloseBtn.bottom);
		}
	}

	void DrawCaption(CDCHandle dc, bool bVertical, RECT &rc)
	{      
		if( ::IsRectEmpty(&rc) )
			return;

		T* pT = static_cast<T*>(this);

		RECT rcText = rc;
		if (bVertical)
			rcText.top = m_rcPushpinBtn.bottom + 6;
		else
			rcText.left = m_rcPushpinBtn.right + 6;
		pT->DrawCaptionText(dc, bVertical, rcText);
		pT->DrawCloseButton(dc, bVertical, m_rcCloseBtn, m_bCloseDown);
		bool bPushpinDowned = (m_pCtx->dwFlags & snapPinned) != 0;
		if (m_bPushpinDown)
			bPushpinDowned = !bPushpinDowned; //reverse
		pT->DrawPushpinButton(dc, bVertical, m_rcPushpinBtn, bPushpinDowned);
	}

	void DrawCloseButton(CDCHandle dc, bool /*bVertical*/, const RECT &rc, bool bPushed)
	{
		if( ::IsRectEmpty(&rc) )
			return;

		RECT rcClose = rc;
		dc.DrawFrameControl(&rcClose, DFC_CAPTION, bPushed ? DFCS_CAPTIONCLOSE|DFCS_PUSHED : DFCS_CAPTIONCLOSE);
	}

	void DrawPushpinButton(CDCHandle dc, bool /*bVertical*/, const RECT &rc, bool bPushed)
	{
		if( ::IsRectEmpty(&rc) )
			return;

		RECT rcPushpin = rc;
		dc.DrawFrameControl(&rcPushpin, DFC_BUTTON, DFCS_ADJUSTRECT|DFCS_BUTTONPUSH | (bPushed ? DFCS_PUSHED : 0));

		POINT ptPin = { rcPushpin.left - 1, rcPushpin.top - 1 + (rcPushpin.bottom - rcPushpin.top) / 2};
		if (bPushed)
		{
			ptPin.x += 1;
			ptPin.y += 1;
		}

		POINT ptsPushpin[] = {	{ptPin.x, ptPin.y},
								{ptPin.x + 2, ptPin.y},
								{ptPin.x + 2, ptPin.y - 3},
								{ptPin.x + 4, ptPin.y - 1},
								{ptPin.x + 5, ptPin.y - 2},
								{ptPin.x + 6, ptPin.y - 2},
								{ptPin.x + 6, ptPin.y + 2},
								{ptPin.x + 5, ptPin.y + 2},
								{ptPin.x + 4, ptPin.y + 1},
								{ptPin.x + 2, ptPin.y + 3},
								{ptPin.x + 2, ptPin.y} };

		dc.Polyline(&ptsPushpin[0], 11);
	}

	void DrawCaptionText(CDCHandle dc, bool bVertical, RECT &rc)
	{
		TCHAR szCaption[80];
		::GetWindowText(m_pCtx->hWndView, szCaption, sizeof(szCaption)/sizeof(TCHAR));
		dc.SetBkMode(TRANSPARENT);

		HFONT fontOld = dc.SelectFont(bVertical ? m_vFontSmCaption : m_hFontSmCaption);
		
		if (bVertical)
		{
			dc.TextOut(rc.right, rc.top, szCaption);
		}
		else
		{
			dc.DrawText(szCaption, ::lstrlen(szCaption), &rc, DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS|DT_NOPREFIX);
		}
		dc.SelectFont(fontOld);
	}

	void CheckAutoHide()
	{
		T* pT = static_cast<T*>(this);
		if ( IsFloating(m_pCtx) || (m_pCtx->dwFlags & snapPinned) != 0 || (m_pCtx->dwFlags & snapMinibar) != 0)
		{
			::KillTimer(m_hWnd, pT->IDT_AUTOHIDE);
		}
		else
		{
			::SetTimer(m_hWnd, pT->IDT_AUTOHIDE, pT->IDT_INTERVAL, NULL);
		}
	}

	void ExpandWindow()
	{
		if ( (m_pCtx->dwFlags & snapMinibar) == 0 )
			return; // Already expanded

		// Remove minibar flag (expanded state)
		m_pCtx->dwFlags &= ~snapMinibar;

		int cx = m_size.cx;
		int cy = m_size.cy;
		
		RECT rcWin;
		GetWindowRect(&rcWin);

		DWORD dwPos = m_pCtx->dwFlags & snapPosition;

		if (dwPos & snapLeft)
		{
			rcWin.right = rcWin.left + cx;
		}
		else if (dwPos & snapRight)
		{
			rcWin.left = rcWin.right - cx;
		}
		else if (dwPos & snapTop)
		{
			rcWin.bottom = rcWin.top + cy;
		}
		else if (dwPos & snapBottom)
		{
			rcWin.top = rcWin.bottom - cy;
		}

		::MapWindowPoints(HWND_DESKTOP, GetParent(), (LPPOINT)&rcWin, sizeof(RECT)/sizeof(POINT));
		SetWindowPos(HWND_TOP,rcWin.left,rcWin.top,rcWin.right-rcWin.left,rcWin.bottom-rcWin.top,SWP_NOACTIVATE); //|SWP_NOZORDER
	}

	void ShrinkWindow()
	{
		if ( m_pCtx->dwFlags & snapMinibar )
			return; // Already minimized

		// Set minibar flag (minimized state)
		m_pCtx->dwFlags |= snapMinibar;

		// Save window size
		RECT rcWindow;
		GetWindowRect(&rcWindow);
		m_size.cx = rcWindow.right-rcWindow.left;
		m_size.cy = rcWindow.bottom-rcWindow.top;

		// Autohide according to snapped sides
		RECT rcMinibar = rcWindow;
		::MapWindowPoints(HWND_DESKTOP, GetParent(), (LPPOINT)&rcMinibar, sizeof(RECT)/sizeof(POINT));

		DWORD dwPos = m_pCtx->dwFlags & snapPosition;

		if (dwPos & snapLeft)
		{
			rcMinibar.right = rcMinibar.left + m_cxyCaption + m_cxyBorder;
		}
		else if (dwPos & snapRight)
		{
			rcMinibar.left = rcMinibar.right - m_cxyCaption - m_cxyBorder;
		}
		else if (dwPos & snapTop)
		{
			rcMinibar.bottom = rcMinibar.top + m_cxyCaption + m_cxyBorder;
		}
		else if (dwPos & snapBottom)
		{
			rcMinibar.top = rcMinibar.bottom - m_cxyCaption - m_cxyBorder;
		}

		SetWindowPos(NULL,rcMinibar.left,rcMinibar.top,rcMinibar.right-rcMinibar.left,rcMinibar.bottom-rcMinibar.top,SWP_NOACTIVATE|SWP_NOZORDER);
		return;
	}
};

class CSnapAutoHideWindow : public CSnapAutoHideWindowImpl<CSnapAutoHideWindow>
{
public:
	DECLARE_WND_CLASS_EX(_T("WTL_SnapAutoHideWindow"), CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, NULL)

	CSnapAutoHideWindow(SNAPCONTEXT* pCtx) : CSnapAutoHideWindowImpl<CSnapAutoHideWindow>(pCtx)
	{ 
	}
};


/////////////////////////////////////////////////////////////////////
// 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 >
{
public:
	DECLARE_WND_CLASS_EX(NULL, CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, NULL)

	typedef CSnappingWindowImpl< T, TSnappedWindow, TFloatingWindow, TBase, TWinTraits > thisClass;
	BEGIN_MSG_MAP(thisClass)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_SNAP_SNAP,   OnSnapWindow)
		MESSAGE_HANDLER(WM_SNAP_FLOAT, OnFloatWindow)
		MESSAGE_HANDLER(WM_SNAP_HIDE, OnHideWindow)
		MESSAGE_HANDLER(WM_SNAP_QUERYRECT, OnSnapQueryRect)
	END_MSG_MAP()

	void SetClient(HWND hWndClient)
	{
		m_hWndClient = hWndClient;
	}

	inline HWND GetClient()
	{
		return m_hWndClient;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		for( int i=0; i<m_snappableWindows.GetSize(); i++ ) 
		{
			if (::IsWindow(m_snappableWindows[i]->hWndSnapped))
				::DestroyWindow(m_snappableWindows[i]->hWndSnapped);
			if (::IsWindow(m_snappableWindows[i]->hWndFloated))
				::DestroyWindow(m_snappableWindows[i]->hWndFloated);
			
			delete m_snappableWindows[i];
		}
		m_snappableWindows.RemoveAll();
		m_snappedWindows.RemoveAll();
		m_floatedWindows.RemoveAll();
		return 1;
	}

	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 1;
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		RECT rect;
		GetClientRect(&rect);
		T* pT = static_cast<T*>(this);

		::MapWindowPoints(m_hWnd, ::GetParent( pT->GetClient() ), (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
		::SetWindowPos( pT->GetClient() , HWND_BOTTOM, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0);

		pT->QueryRect(rect);

		for( int i=0; i<m_snappedWindows.GetSize(); i++ ) 
		{
			::SendMessage(m_snappedWindows[i]->hWndSnapped, WM_SNAP_REPOSITION, 0, (LPARAM)&rect);
		}
		return 0;
	}

	LRESULT OnSnapQueryRect(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		RECT* pRect = reinterpret_cast<RECT*>(lParam);
		ATLASSERT(pRect != NULL);
		if (!pRect)
			return -1;
		pT->QueryRect(*pRect);
		return 0;
	}

	LRESULT OnSnapWindow(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		SNAPWINDOWINFO* pswi= (SNAPWINDOWINFO*) wParam;
		SNAPCONTEXT* pCtx = (SNAPCONTEXT*) lParam;
		ATLASSERT(pswi != NULL);
		ATLASSERT(pCtx != NULL);
		if (!pswi || !pCtx)
			return -1;
		T* pT = static_cast<T*>(this);
		if (!IsHidden(pCtx->dwFlags))
			pT->UnfloatWindow(pCtx);
		pT->SnapWindow(pCtx, pswi->snapPosition, pswi->cxy, pswi->dwFlags);
		return 0;
	}
	LRESULT OnFloatWindow(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		SNAPWINDOWINFO* pswi= (SNAPWINDOWINFO*) wParam;
		SNAPCONTEXT* pCtx = (SNAPCONTEXT*) lParam;
		ATLASSERT(pswi != NULL);
		ATLASSERT(pCtx != NULL);
		if (!pswi || !pCtx)
			return -1;
		T* pT = static_cast<T*>(this);
		if (!IsHidden(pCtx->dwFlags))
			pT->UnsnapWindow(pCtx);
		pT->FloatWindow(pCtx, pswi->ptPosition, pswi->dwFlags);
		return 0;
	}
	LRESULT OnHideWindow(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		SNAPCONTEXT* pCtx = (SNAPCONTEXT*) lParam;
		ATLASSERT(pCtx != NULL);
		if (!pCtx)
			return -1;
		T* pT = static_cast<T*>(this);
		pT->HideWindow(pCtx);
		return 0;
	}

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

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

	void UnfloatWindow(SNAPCONTEXT* pCtx)
	{
		ATLASSERT( IsFloating(pCtx) );
		pCtx->dwFlags &= ~snapPosition;
		pCtx->dwFlags |= snapHidden;
		for (int i=0; i < m_floatedWindows.GetSize(); ++i)
		{
			if ( m_floatedWindows[i] == pCtx )
			{
				m_floatedWindows.RemoveAt(i);
			}
		}
		::ShowWindow(pCtx->hWndFloated, SW_HIDE);
	}

	void UnsnapWindow(SNAPCONTEXT* pCtx)
	{
		ATLASSERT( ! IsFloating(pCtx) );
		ATLASSERT( ! IsHidden(pCtx) );
		pCtx->dwFlags &= ~snapPosition;
		pCtx->dwFlags |= snapHidden;
		for (int i=0; i < m_snappedWindows.GetSize(); ++i)
		{
			if ( m_snappedWindows[i] == pCtx )
			{
				m_snappedWindows.RemoveAt(i);
			}
		}
		::ShowWindow(pCtx->hWndSnapped, SW_HIDE);
	}

	SNAPCONTEXT* GetSnapContext(HWND hWndView)
	{
		for( int i=0; i<m_snappableWindows.GetSize(); i++ ) 
		{
			if ( m_snappableWindows[i]->hWndView == hWndView)
				return m_snappableWindows[i];
		}
		return NULL;
	}

	void FloatWindow(HWND hWndView, const POINT& ptPos, DWORD dwFlags = 0)
	{
		T* pT = static_cast<T*>(this);
		SNAPCONTEXT* pCtx = pT->GetSnapContext(hWndView);
		if (pCtx)
		{
			pT->FloatWindow(pCtx, ptPos, dwFlags);
		}
	}
	void SnapWindow(HWND hWndView, SnapPosition spPos, int cxy = 0, DWORD dwFlags = snapMinibar)
	{
		T* pT = static_cast<T*>(this);
		SNAPCONTEXT* pCtx = pT->GetSnapContext(hWndView);
		if (pCtx)
		{
			pT->SnapWindow(pCtx, spPos, cxy, dwFlags);
		}
	}
	void HideWindow(HWND hWndView)
	{
		T* pT = static_cast<T*>(this);
		SNAPCONTEXT* pCtx = pT->GetSnapContext(hWndView);
		if (pCtx)
		{
			pT->HideWindow(pCtx);
		}
	}

	void FloatWindow(SNAPCONTEXT* pCtx, const POINT& ptPos, DWORD dwFlags)
	{
		ATLASSERT( IsHidden(pCtx) );
		pCtx->dwFlags &= ~snapHidden;
		pCtx->dwFlags |= snapFloat;
		m_floatedWindows.Add(pCtx);

		// Retrieve size of view
		RECT rcView;
		::GetWindowRect(pCtx->hWndView, &rcView);
		// Adjust the size for the floating window
		SIZE size = {rcView.right - rcView.left,  rcView.bottom - rcView.top};
		::SendMessage(pCtx->hWndFloated,WM_SNAP_QUERYSIZE, pCtx->dwFlags, (LPARAM)&size);

		// Changed parent of view
		::SetParent(pCtx->hWndView, pCtx->hWndFloated);
		::SetWindowPos(pCtx->hWndFloated, NULL, ptPos.x, ptPos.y, size.cx, size.cy, SWP_NOZORDER);

		// Reposition the view
		RECT rc;
		::GetClientRect(pCtx->hWndFloated, &rc);
		::SetWindowPos(pCtx->hWndView, NULL, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER|SWP_NOACTIVATE);
		::ShowWindow(pCtx->hWndFloated,SW_SHOWNORMAL);
		::ShowWindow(pCtx->hWndView,SW_SHOWNOACTIVATE);
	}


	void SnapWindow(SNAPCONTEXT* pCtx, SnapPosition spPos, int cxy, DWORD dwFlags)
	{
		ATLASSERT( IsHidden(pCtx) );
		pCtx->dwFlags &= ~snapHidden;
		pCtx->dwFlags |= spPos;
		m_snappedWindows.Add(pCtx);

		// Retrieve size of view
		RECT rcView;
		::GetWindowRect(pCtx->hWndView, &rcView);
		// Adjust the size for the snapped window
		SIZE size = {rcView.right - rcView.left,  rcView.bottom - rcView.top};
		::SendMessage(pCtx->hWndSnapped,WM_SNAP_QUERYSIZE, pCtx->dwFlags, (LPARAM)&size);

		T* pT = static_cast<T*>(this);
		RECT rcLayout;
		pT->QueryRect(rcLayout);

		POINT pos = {rcLayout.left + cxy, rcLayout.top + cxy};

		if (pCtx->dwFlags & snapLeft)
			pos.x = rcLayout.left;
		if (pCtx->dwFlags & snapTop)
			pos.y = rcLayout.top;
		if (pCtx->dwFlags & snapRight)
			pos.x = rcLayout.right - size.cx;
		if (pCtx->dwFlags & snapBottom)
			pos.y = rcLayout.bottom - size.cy;

		::SetParent(pCtx->hWndView, pCtx->hWndSnapped);
		::ScreenToClient(::GetParent(pCtx->hWndSnapped), &pos);
		::SetWindowPos(pCtx->hWndSnapped, NULL, pos.x, pos.y, size.cx, size.cy, SWP_NOZORDER);
		::SendMessage(pCtx->hWndSnapped, WM_SNAP_UPDATELAYOUT, dwFlags, 0);
		::ShowWindow(pCtx->hWndSnapped,SW_SHOWNORMAL);
		::ShowWindow(pCtx->hWndView,SW_SHOWNOACTIVATE);
	}

	void HideWindow(SNAPCONTEXT* pCtx)
	{
		T* pT = static_cast<T*>(this);
		if ( IsHidden(pCtx) )
			return;
		else if ( IsFloating(pCtx) )
			pT->UnfloatWindow(pCtx);
		else
			pT->UnsnapWindow(pCtx);
	}

	HWND           m_hWndClient;
	CSnapContainer m_snappableWindows;
	CSnapContainer m_snappedWindows;
	CSnapContainer m_floatedWindows;
};


class CSnappingWindow : public CSnappingWindowImpl<CSnappingWindow>
{
public:
	DECLARE_WND_CLASS_EX(_T("WTL_SnappingWindow"), CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, NULL)

	CSnappingWindow() : CSnappingWindowImpl<CSnappingWindow>()
	{ 
	}
};

#endif //__WTL_SNAPPABLE_H_

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions