www.pudn.com > TreePropSheetEx.rar > ResizableLayout.cpp


// ResizableLayout.cpp: implementation of the CResizableLayout class. 
// 
///////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) 2000-2002 by Paolo Messina 
// (http://www.geocities.com/ppescher - ppescher@yahoo.com) 
// 
// The contents of this file are subject to the Artistic License (the "License"). 
// You may not use this file except in compliance with the License.  
// You may obtain a copy of the License at: 
// http://www.opensource.org/licenses/artistic-license.html 
// 
// If you find this code useful, credits would be nice! 
// 
///////////////////////////////////////////////////////////////////////////// 
 
#include "stdafx.h" 
#include "ResizableLayout.h" 
#include "ResizableMsgSupport.inl" 
 
#ifdef _DEBUG 
#undef THIS_FILE 
static char THIS_FILE[]=__FILE__; 
#define new DEBUG_NEW 
#endif 
 
// Update for CTreePropSheetEx to allow compilation on machines without  
// Platform SDK installed (Yves Tkaczyk). 
// Define constants that are in the Platform SDK, but not in the default VC6 
// installation. 
// Thank to Don M for suggestion 
// (http://www.codeproject.com/property/TreePropSheetEx.asp?msg=939854#xx939854xx) 
#ifndef WS_EX_LAYOUTRTL 
#define WS_EX_LAYOUTRTL 0x400000 /* w98, w2k */ 
#endif 
 
#ifndef WC_BUTTON 
#define WC_BUTTON _T("Button") 
#endif 
#ifndef WC_STATIC 
#define WC_STATIC _T("Static") 
#endif 
#ifndef WC_LISTBOX 
#define WC_LISTBOX _T("ListBox") 
#endif 
 
 
////////////////////////////////////////////////////////////////////// 
// Construction/Destruction 
////////////////////////////////////////////////////////////////////// 
 
// In August 2002 Platform SDK, some guy at MS thought it was time to 
// add the missing symbol BS_TYPEMASK, but forgot its original meaning 
// and so now he's telling us not to use that symbol because its 
// value is likely to change in the future SDK releases, including all 
// the BS_* style bits in the mask, not just the button's type as the 
// symbol's name suggests. So now we're forced to use another symbol! 
#define _BS_TYPEMASK 0x0000000FL 
 
void CResizableLayout::AddAnchor(HWND hWnd, CSize sizeTypeTL, CSize sizeTypeBR) 
{ 
	CWnd* pParent = GetResizableWnd(); 
 
	// child window must be valid 
	ASSERT(::IsWindow(hWnd)); 
	// must be child of parent window 
	ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd)); 
	// top-left anchor must be valid 
	ASSERT(sizeTypeTL != NOANCHOR); 
 
	// get control's window class 
	CString sClassName; 
	GetClassName(hWnd, sClassName.GetBufferSetLength(MAX_PATH), MAX_PATH); 
	sClassName.ReleaseBuffer(); 
 
	// get parent window's rect 
	CRect rectParent; 
	GetTotalClientRect(&rectParent); 
	// and child control's rect 
	CRect rectChild; 
	::GetWindowRect(hWnd, &rectChild); 
	::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2); 
 
	// adjust position, if client area has been scrolled 
	rectChild.OffsetRect(-rectParent.TopLeft()); 
 
	// go calculate margins 
	CSize sizeMarginTL, sizeMarginBR; 
 
	if (sizeTypeBR == NOANCHOR) 
		sizeTypeBR = sizeTypeTL; 
	 
	// calculate margin for the top-left corner 
 
	sizeMarginTL.cx = rectChild.left - rectParent.Width() * sizeTypeTL.cx / 100; 
	sizeMarginTL.cy = rectChild.top - rectParent.Height() * sizeTypeTL.cy / 100; 
	 
	// calculate margin for the bottom-right corner 
 
	sizeMarginBR.cx = rectChild.right - rectParent.Width() * sizeTypeBR.cx / 100; 
	sizeMarginBR.cy = rectChild.bottom - rectParent.Height() * sizeTypeBR.cy / 100; 
 
	// prepare the structure 
	LayoutInfo layout(hWnd, sizeTypeTL, sizeMarginTL, 
		sizeTypeBR, sizeMarginBR, sClassName); 
 
	// initialize resize properties (overridable) 
	InitResizeProperties(layout); 
 
	// must not be already there! 
	// (this is probably due to a duplicate call to AddAnchor) 
	POSITION pos; 
	ASSERT(!m_mapLayout.Lookup(hWnd, pos)); 
 
	// add to the list and the map 
	pos = m_listLayout.AddTail(layout); 
	m_mapLayout.SetAt(hWnd, pos); 
} 
 
void CResizableLayout::AddAnchorCallback(UINT nCallbackID) 
{ 
	// one callback control cannot rely upon another callback control's 
	// size and/or position (they're updated all together at the end) 
	// it can however use a non-callback control, which is updated before 
 
	// add to the list 
	LayoutInfo layout; 
	layout.nCallbackID = nCallbackID; 
	m_listLayoutCB.AddTail(layout); 
} 
 
BOOL CResizableLayout::ArrangeLayoutCallback(CResizableLayout::LayoutInfo& /*layout*/) 
{ 
	ASSERT(FALSE); 
	// must be overridden, if callback is used 
	 
	return FALSE;	// no output data 
} 
 
void CResizableLayout::ArrangeLayout() 
{ 
	// common vars 
	UINT uFlags; 
	LayoutInfo layout; 
	CRect rectParent, rectChild; 
	GetTotalClientRect(&rectParent); // get parent window's rect 
	int count = m_listLayout.GetCount(); 
	int countCB = m_listLayoutCB.GetCount(); 
 
	// reposition child windows 
	HDWP hdwp = ::BeginDeferWindowPos(count + countCB); 
	 
	POSITION pos = m_listLayout.GetHeadPosition(); 
	while (pos != NULL) 
	{ 
		// get layout info 
		layout = m_listLayout.GetNext(pos); 
		 
		// calculate new child's position, size and flags for SetWindowPos 
		CalcNewChildPosition(layout, rectParent, rectChild, uFlags); 
 
		// only if size or position changed 
		if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE)) 
		{ 
			hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left, 
				rectChild.top, rectChild.Width(), rectChild.Height(), uFlags); 
		} 
	} 
 
	// for callback items you may use GetAnchorPosition to know the 
	// new position and size of a non-callback item after resizing 
 
	pos = m_listLayoutCB.GetHeadPosition(); 
	while (pos != NULL) 
	{ 
		// get layout info 
		layout = m_listLayoutCB.GetNext(pos); 
		// request layout data 
		if (!ArrangeLayoutCallback(layout)) 
			continue; 
 
		// calculate new child's position, size and flags for SetWindowPos 
		CalcNewChildPosition(layout, rectParent, rectChild, uFlags); 
 
		// only if size or position changed 
		if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE)) 
		{ 
			hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left, 
				rectChild.top, rectChild.Width(), rectChild.Height(), uFlags); 
		} 
	} 
 
	// finally move all the windows at once 
	::EndDeferWindowPos(hdwp); 
} 
 
void CResizableLayout::ClipChildWindow(const CResizableLayout::LayoutInfo& layout, 
									   CRgn* pRegion) 
{ 
	// obtain window position 
	CRect rect; 
	::GetWindowRect(layout.hWnd, &rect); 
	::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2); 
 
	// use window region if any 
	CRgn rgn; 
	rgn.CreateRectRgn(0,0,0,0); 
	switch (::GetWindowRgn(layout.hWnd, rgn)) 
	{ 
	case COMPLEXREGION: 
	case SIMPLEREGION: 
		rgn.OffsetRgn(rect.TopLeft()); 
		break; 
 
	default: 
		rgn.SetRectRgn(&rect); 
	} 
 
	// get the clipping property 
	BOOL bClipping = layout.properties.bAskClipping ? 
		LikesClipping(layout) : layout.properties.bCachedLikesClipping; 
 
	// modify region accordingly 
	if (bClipping) 
		pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF); 
	else 
		pRegion->CombineRgn(pRegion, &rgn, RGN_OR); 
} 
 
void CResizableLayout::GetClippingRegion(CRgn* pRegion) 
{ 
	CWnd* pWnd = GetResizableWnd(); 
 
	// System's default clipping area is screen's size, 
	// not enough for max track size, for example: 
	// if screen is 1024 x 768 and resizing border is 4 pixels, 
	// maximized size is 1024+4*2=1032 x 768+4*2=776, 
	// but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!) 
	// So, if you resize the window to maximum size, the last 4 pixels 
	// are clipped out by the default clipping region, that gets created 
	// as soon as you call clipping functions (my guess). 
 
	// reset clipping region to the whole client area 
	CRect rect; 
	pWnd->GetClientRect(&rect); 
	pRegion->CreateRectRgnIndirect(&rect); 
 
	// clip only anchored controls 
	LayoutInfo layout; 
	POSITION pos = m_listLayout.GetHeadPosition(); 
	while (pos != NULL) 
	{ 
		// get layout info 
		layout = m_listLayout.GetNext(pos); 
		 
		if (::IsWindowVisible(layout.hWnd)) 
			ClipChildWindow(layout, pRegion); 
	} 
	pos = m_listLayoutCB.GetHeadPosition(); 
	while (pos != NULL) 
	{ 
		// get layout info 
		layout = m_listLayoutCB.GetNext(pos); 
		// request data 
		if (!ArrangeLayoutCallback(layout)) 
			continue; 
 
		if (::IsWindowVisible(layout.hWnd)) 
			ClipChildWindow(layout, pRegion); 
	} 
 
	// fix for RTL layouts (1 pixel of horz offset) 
	if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL) 
		pRegion->OffsetRgn(-1,0); 
} 
 
void CResizableLayout::EraseBackground(HDC hDC) 
{ 
	HWND hWnd = GetResizableWnd()->GetSafeHwnd(); 
 
	// retrieve the background brush 
	HBRUSH hBrush = NULL; 
 
	// is this a dialog box? 
	// (using class atom is quickier than using the class name) 
	ATOM atomWndClass = (ATOM)::GetClassLong(hWnd, GCW_ATOM); 
	if (atomWndClass == (ATOM)0x8002) 
	{ 
		// send a message to the dialog box 
		hBrush = (HBRUSH)::SendMessage(hWnd, WM_CTLCOLORDLG, 
			(WPARAM)hDC, (LPARAM)hWnd); 
	} 
	else 
	{ 
		// take the background brush from the window's class 
		hBrush = (HBRUSH)::GetClassLong(hWnd, GCL_HBRBACKGROUND); 
	} 
 
	// fill the clipped background 
	CRgn rgn; 
	GetClippingRegion(&rgn); 
 
  ::FillRgn(hDC, rgn, hBrush); 
} 
 
void CResizableLayout::EraseBackground(CDC* pDC) 
{ 
//	HWND hWnd = GetResizableWnd()->GetSafeHwnd(); 
// 
//	// retrieve the background brush 
//	HBRUSH hBrush = NULL; 
// 
//	// is this a dialog box? 
//	// (using class atom is quickier than using the class name) 
//	ATOM atomWndClass = (ATOM)::GetClassLong(hWnd, GCW_ATOM); 
//	if (atomWndClass == (ATOM)0x8002) 
//	{ 
//		// send a message to the dialog box 
//		hBrush = (HBRUSH)::SendMessage(hWnd, WM_CTLCOLORDLG, 
//			(WPARAM)pDC->GetSafeHdc(), (LPARAM)hWnd); 
//	} 
//	else 
//	{ 
//		// take the background brush from the window's class 
//		hBrush = (HBRUSH)::GetClassLong(hWnd, GCL_HBRBACKGROUND); 
//	} 
// 
//	// fill the clipped background 
//	CRgn rgn; 
//	GetClippingRegion(&rgn); 
// 
//	::FillRgn(pDC->GetSafeHdc(), rgn, hBrush); 
  EraseBackground( pDC->GetSafeHdc() ); 
} 
 
// support legacy code (will disappear in future versions) 
void CResizableLayout::ClipChildren(CDC* pDC) 
{ 
	CRgn rgn; 
	GetClippingRegion(&rgn); 
	// the clipping region is in device units 
	rgn.OffsetRgn(-pDC->GetWindowOrg()); 
	pDC->SelectClipRgn(&rgn); 
} 
 
void CResizableLayout::GetTotalClientRect(LPRECT lpRect) 
{ 
	GetResizableWnd()->GetClientRect(lpRect); 
} 
 
BOOL CResizableLayout::NeedsRefresh(const CResizableLayout::LayoutInfo& layout, 
									const CRect& rectOld, const CRect& rectNew) 
{ 
	if (layout.bMsgSupport) 
	{ 
		REFRESHPROPERTY refresh; 
		refresh.rcOld = rectOld; 
		refresh.rcNew = rectNew; 
		if (Send_NeedsRefresh(layout.hWnd, &refresh)) 
			return refresh.bNeedsRefresh; 
	} 
 
	int nDiffWidth = (rectNew.Width() - rectOld.Width()); 
	int nDiffHeight = (rectNew.Height() - rectOld.Height()); 
 
	// is the same size? 
	if (nDiffWidth == 0 && nDiffHeight == 0) 
		return FALSE; 
 
	// optimistic, no need to refresh 
	BOOL bRefresh = FALSE; 
 
	// window classes that need refresh when resized 
	if (layout.sWndClass == WC_STATIC) 
	{ 
		DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE); 
 
		switch (style & SS_TYPEMASK) 
		{ 
		case SS_LEFT: 
		case SS_CENTER: 
		case SS_RIGHT: 
			// word-wrapped text 
			bRefresh = bRefresh || (nDiffWidth != 0); 
			// vertically centered text 
			if (style & SS_CENTERIMAGE) 
				bRefresh = bRefresh || (nDiffHeight != 0); 
			break; 
 
		case SS_LEFTNOWORDWRAP: 
			// text with ellipsis 
			if (style & SS_ELLIPSISMASK) 
				bRefresh = bRefresh || (nDiffWidth != 0); 
			// vertically centered text 
			if (style & SS_CENTERIMAGE) 
				bRefresh = bRefresh || (nDiffHeight != 0); 
			break; 
 
		case SS_ENHMETAFILE: 
		case SS_BITMAP: 
		case SS_ICON: 
			// images 
		case SS_BLACKFRAME: 
		case SS_GRAYFRAME: 
		case SS_WHITEFRAME: 
		case SS_ETCHEDFRAME: 
			// and frames 
			bRefresh = TRUE; 
			break; 
		} 
	} 
 
	// window classes that don't redraw client area correctly 
	// when the hor scroll pos changes due to a resizing 
	BOOL bHScroll = FALSE; 
	if (layout.sWndClass == WC_LISTBOX) 
		bHScroll = TRUE; 
 
	// fix for horizontally scrollable windows 
	if (bHScroll && (nDiffWidth > 0)) 
	{ 
		// get max scroll position 
		SCROLLINFO info; 
		info.cbSize = sizeof(SCROLLINFO); 
		info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; 
		if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info)) 
		{ 
			// subtract the page size 
			info.nMax -= __max(info.nPage-1,0); 
		} 
 
		// resizing will cause the text to scroll on the right 
		// because the scrollbar is going beyond the right limit 
		if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax)) 
		{ 
			// needs repainting, due to horiz scrolling 
			bRefresh = TRUE; 
		} 
	} 
 
	return bRefresh; 
} 
 
BOOL CResizableLayout::LikesClipping(const CResizableLayout::LayoutInfo& layout) 
{ 
	if (layout.bMsgSupport) 
	{ 
		CLIPPINGPROPERTY clipping; 
		if (Send_LikesClipping(layout.hWnd, &clipping)) 
			return clipping.bLikesClipping; 
	} 
 
	DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE); 
 
	// skip windows that wants background repainted 
	if (layout.sWndClass == TOOLBARCLASSNAME && (style & TBSTYLE_TRANSPARENT)) 
		return FALSE; 
	else if (layout.sWndClass == WC_BUTTON) 
	{ 
		CRect rect; 
		switch (style & _BS_TYPEMASK) 
		{ 
		case BS_GROUPBOX: 
			return FALSE; 
 
		case BS_OWNERDRAW: 
			// ownerdraw buttons must return correct hittest code 
			// to notify their transparency to the system and this library 
			::GetWindowRect(layout.hWnd, &rect); 
			if ( HTTRANSPARENT == ::SendMessage(layout.hWnd, 
				WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) ) 
				return FALSE; 
			break; 
		} 
		return TRUE; 
	} 
	else if (layout.sWndClass == WC_STATIC) 
	{ 
		switch (style & SS_TYPEMASK) 
		{ 
		case SS_LEFT: 
		case SS_CENTER: 
		case SS_RIGHT: 
		case SS_SIMPLE: 
		case SS_LEFTNOWORDWRAP: 
			// text 
		case SS_BLACKRECT: 
		case SS_GRAYRECT: 
		case SS_WHITERECT: 
			// filled rects 
		case SS_ETCHEDHORZ: 
		case SS_ETCHEDVERT: 
			// etched lines 
		case SS_BITMAP: 
			// bitmaps 
			return TRUE; 
			break; 
 
		case SS_ICON: 
		case SS_ENHMETAFILE: 
			if (style & SS_CENTERIMAGE) 
				return FALSE; 
			return TRUE; 
			break; 
 
		default: 
			return FALSE; 
		} 
	} 
 
	// assume the others like clipping 
	return TRUE; 
} 
 
void CResizableLayout::CalcNewChildPosition(const CResizableLayout::LayoutInfo& layout, 
								const CRect &rectParent, CRect &rectChild, UINT& uFlags) 
{ 
	CWnd* pParent = GetResizableWnd(); 
 
	::GetWindowRect(layout.hWnd, &rectChild); 
	::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2); 
	 
	CRect rectNew; 
 
	// calculate new top-left corner 
	rectNew.left = layout.sizeMarginTL.cx + rectParent.Width() * layout.sizeTypeTL.cx / 100; 
	rectNew.top = layout.sizeMarginTL.cy + rectParent.Height() * layout.sizeTypeTL.cy / 100; 
	 
	// calculate new bottom-right corner 
	rectNew.right = layout.sizeMarginBR.cx + rectParent.Width() * layout.sizeTypeBR.cx / 100; 
	rectNew.bottom = layout.sizeMarginBR.cy + rectParent.Height() * layout.sizeTypeBR.cy / 100; 
 
	// adjust position, if client area has been scrolled 
	rectNew.OffsetRect(rectParent.TopLeft()); 
 
	// get the refresh property 
	BOOL bRefresh = layout.properties.bAskRefresh ? 
		NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh; 
 
	// set flags  
	uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION; 
	if (bRefresh) 
		uFlags |= SWP_NOCOPYBITS; 
	if (rectNew.TopLeft() == rectChild.TopLeft()) 
		uFlags |= SWP_NOMOVE; 
	if (rectNew.Size() == rectChild.Size()) 
		uFlags |= SWP_NOSIZE; 
 
	// update rect 
	rectChild = rectNew; 
} 
 
void CResizableLayout::InitResizeProperties(CResizableLayout::LayoutInfo &layout) 
{ 
	// check if custom window supports this library 
	// (properties must be correctly set by the window) 
	layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties); 
 
	// default properties 
	if (!layout.bMsgSupport) 
	{ 
		// clipping property is assumed as static 
		layout.properties.bAskClipping = FALSE; 
		layout.properties.bCachedLikesClipping = LikesClipping(layout); 
		// refresh property is assumed as dynamic 
		layout.properties.bAskRefresh = TRUE; 
	} 
}