Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

ResizableLib

, 15 Jun 2004
A set of classes to ease the development of resizable windows with MFC
resizabledialog_demo_1_4a.zip
ResizableDialog
Demo.dsp
Demo.dsw
Release_Static
Demo.exe
Demo.exe.manifest
res
bitmap1.bmp
Demo.ico
toolbar1.bmp
resizableformviewmdi_demo_1_4a.zip
ResizableFormViewMDI
Demo.dsp
Demo.dsw
Release_Static
Demo.exe
Demo.exe.manifest
res
Demo.ico
DemoDoc.ico
DemoDoc0.ico
DemoDoc1.ico
DemoDoc2.ico
resizableformviewsdi_demo_1_4a.zip
ResizableFormViewSDI
Demo.dsp
Demo.dsw
Release_Static
Demo.exe
Demo.exe.manifest
res
Demo.ico
DemoDoc.ico
Toolbar.bmp
resizablelib_1_1.zip
ResizableLib
.cvsignore
Docs
CVS
Entries
Repository
Root
Release_Static
ResizableLib.lib
ResizableLib.dsp
ResizableLib.dsw
resizablelib_1_2.zip
.cvsignore
ResizableLib.dsp
ResizableLib.dsw
resizablelib_1_3.zip
ChangeLog
ResizableLib.dsp
ResizableLib.dsw
ResizableMsgSupport.inl
resizablelib_1_4a.zip
Doxyfile-include
Doxyfile
ResizableLib.dsp
ResizableLib.dsw
resizablelib_src.zip
ResizableLib.dsp
ResizableLib.dsw
.cvsignore
resizableproperties_demo_1_4a.zip
ResizableProperties
Demo.dsp
Demo.dsw
Release_Static
Demo.exe
Demo.exe.manifest
res
Demo.ico
resizablewizard97_demo_1_4a.zip
ResizableWizard97
Release_Static
Wizard97.exe
Wizard97.exe.manifest
res
Baner16.bmp
Baner256.bmp
Water16.bmp
Water256.bmp
Wizard97.ico
Wizard97.dsp
Wizard97.dsw
/////////////////////////////////////////////////////////////////////////////
//
// This file is part of ResizableLib
// http://sourceforge.net/projects/resizablelib
//
// Copyright (C) 2000-2004 by Paolo Messina
// http://www.geocities.com/ppescher - mailto:ppescher@hotmail.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!
//
/////////////////////////////////////////////////////////////////////////////

/*!
 *  @file
 *  @brief Implementation of the CResizableLayout class.
 */

#include "stdafx.h"
#include "ResizableLayout.h"
#include "ResizableVersion.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

/*!
 *  @internal Constant used to detect clipping and refresh properties
 *
 *  @note 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.
 *     @n So now we're forced to define another symbol, great!
 */
#define _BS_TYPEMASK 0x0000000FL

/*!
 *  This function adds a new control to the layout manager and sets anchor
 *  points for its top-left and bottom-right corners.
 *  
 *  @param hWnd Window handle to the control to be added
 *  @param anchorTopLeft Anchor point for the top-left corner
 *  @param anchorBottomRight Anchor point for the bottom-right corner
 *
 *  @remarks Overlapping controls, like group boxes and the controls inside,
 *           must be added from the outer controls to the inner ones, to let
 *           the clipping routines work correctly.
 *
 *  @sa AddAnchorCallback RemoveAnchor
 */
void CResizableLayout::AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
{
	CWnd* pParent = GetResizableWnd();

	// child window must be valid
	ASSERT(::IsWindow(hWnd));
	// must be child of parent window
	ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));

	// 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 marginTopLeft, marginBottomRight;

	// calculate margin for the top-left corner

	marginTopLeft.cx = rectChild.left - rectParent.Width() * anchorTopLeft.cx / 100;
	marginTopLeft.cy = rectChild.top - rectParent.Height() * anchorTopLeft.cy / 100;
	
	// calculate margin for the bottom-right corner

	marginBottomRight.cx = rectChild.right - rectParent.Width() * anchorBottomRight.cx / 100;
	marginBottomRight.cy = rectChild.bottom - rectParent.Height() * anchorBottomRight.cy / 100;

	// prepare the structure
	LAYOUTINFO layout(hWnd, anchorTopLeft, marginTopLeft,
		anchorBottomRight, marginBottomRight);

	// get control's window class
	GetClassName(hWnd, layout.sWndClass, MAX_PATH);

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

/*!
 *  This function adds a placeholder to the layout manager, that will be
 *  dinamically set by a callback function whenever required.
 *
 *  @return The return value is an integer used to distinguish between
 *          different placeholders in the callback implementation.
 *
 *  @remarks You must override @ref ArrangeLayoutCallback to provide layout
 *           information.
 *
 *  @sa AddAnchor ArrangeLayoutCallback ArrangeLayout
 */
UINT CResizableLayout::AddAnchorCallback()
{
	// 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, calling GetAnchorPosition()

	// add to the list
	LAYOUTINFO layout;
	layout.nCallbackID = m_listLayoutCB.GetCount() + 1;
	m_listLayoutCB.AddTail(layout);
	return layout.nCallbackID;
}

/*!
 *  This function is called for each placeholder added to the layout manager
 *  and must be overridden to provide the necessary layout information.
 *  
 *  @param layout Reference to a LAYOUTINFO structure to be filled with
 *         layout information for the specified placeholder.
 *         On input, nCallbackID is the identification number
 *         returned by AddAnchorCallback. On output, anchor points and
 *         the window handle must be set and valid.
 *  
 *  @return The return value is @c TRUE if the layout information has been
 *          provided successfully, @c FALSE to skip this placeholder.
 *
 *  @remarks When implementing this function, unknown placeholders should be
 *           passed to the base class. Unhandled cases will fire an assertion
 *           in the debug version.
 *
 *  @sa AddAnchorCallback ArrangeLayout LAYOUTINFO
 */
BOOL CResizableLayout::ArrangeLayoutCallback(LAYOUTINFO& layout) const
{
	UNREFERENCED_PARAMETER(layout);
	
	ASSERT(FALSE); // must be overridden, if callback is used
	
	return FALSE; // no useful output data
}

/*!
 *  This function should be called in resizable window classes whenever the
 *  controls layout should be updated, usually after a resize operation.
 *
 *  @remarks All the controls added to the layout are moved and resized at
 *           once for performace reasons, so all the controls are in their
 *           old position when AddAnchorCallback is called.
 *           To know where a control will be placed use GetAnchorPosition.
 *  
 *  @sa AddAnchor AddAnchorCallback ArrangeLayoutCallback GetAnchorPosition
 */
void CResizableLayout::ArrangeLayout() const
{
	// common vars
	UINT uFlags;
	LAYOUTINFO layout;
	CRect rectParent, rectChild;
	int count = m_listLayout.GetCount();
	int countCB = m_listLayoutCB.GetCount();

	if (count + countCB == 0)
		return;

	// get parent window's rect
	GetTotalClientRect(&rectParent);

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

/*!
 *  @internal This function adds or removes a control window region
 *  to or from the specified clipping region, according to its layout
 *  properties.
 */
void CResizableLayout::ClipChildWindow(const LAYOUTINFO& layout,
									   CRgn* pRegion) const
{
	// obtain window position
	CRect rect;
	::GetWindowRect(layout.hWnd, &rect);
#if (_WIN32_WINNT >= 0x0501)
	//! @todo decide when to clip client only or non-client too (themes?)
	//! (leave disabled meanwhile, until I find a good solution)
	//! @note wizard97 with watermark bitmap and themes won't look good!
	// if (real_WIN32_WINNT >= 0x501)
	//	::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
#endif
	::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);
}

/*!
 *  This function retrieves the clipping region for the current layout.
 *  It can be used to draw directly inside the region, without applying
 *  clipping as the ClipChildren function does.
 *  
 *  @param pRegion Pointer to a CRegion object that holds the
 *         calculated clipping region upon return
 *
 *  @deprecated For anti-flickering ClipChildren should be preferred
 *              as it is more complete for platform compatibility.
 *              It will probably become a private function.
 */
void CResizableLayout::GetClippingRegion(CRgn* pRegion) const
{
	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);
	}
//! @todo Has XP changed this??? It doesn't seem correct anymore!
/*
	// fix for RTL layouts (1 pixel of horz offset)
	if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL)
		pRegion->OffsetRgn(-1,0);
*/
}

//! @internal @brief Implements GetAncestor(pWnd->GetSafeHwnd(), GA_ROOT)
inline CWnd* GetRootParentWnd(CWnd* pWnd)
{
	// GetAncestor API not present, emulate
	if (!(pWnd->GetStyle() & WS_CHILD))
		return NULL;
	while (pWnd->GetStyle() & WS_CHILD)
		pWnd = pWnd->GetParent();
	return pWnd;
}

/*!
 *  This function enables or restores clipping on the specified DC when
 *  appropriate. It should be called whenever drawing on the window client
 *  area to avoid flickering.
 *  
 *  @param pDC Pointer to the target device context 
 *  @param bUndo Flag that specifies wether to restore the clipping region
 *  
 *  @return The return value is @c TRUE if the clipping region has been
 *          modified, @c FALSE if clipping was not necessary.
 *
 *  @remarks For anti-flickering to work, you should wrap your
 *           @c WM_ERASEBKGND message handler inside a pair of calls to
 *           this function, with the last parameter set to @c TRUE first
 *           and to @c FALSE at the end.
 */
BOOL CResizableLayout::ClipChildren(CDC* pDC, BOOL bUndo)
{
#if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
	// clipping not necessary when double-buffering enabled
	if (real_WIN32_WINNT >= 0x0501)
	{
		CWnd *pWnd = GetRootParentWnd(GetResizableWnd());
		if (pWnd == NULL)
			pWnd = GetResizableWnd();
		if (pWnd->GetExStyle() & WS_EX_COMPOSITED)
			return FALSE;
	}
#endif

	HDC hDC = pDC->GetSafeHdc();
	HWND hWnd = GetResizableWnd()->GetSafeHwnd();

	m_nOldClipRgn = -1; // invalid region by default

	// Some controls (such as transparent toolbars and standard controls
	// with XP theme enabled) send a WM_ERASEBKGND msg to the parent
	// to draw themselves, in which case we must not enable clipping.

	// We check that the window associated with the DC is the
	// resizable window and not a child control.

	if (!bUndo && (hWnd == ::WindowFromDC(hDC)))
	{
		// save old DC clipping region
		m_nOldClipRgn = ::GetClipRgn(hDC, m_hOldClipRgn);

		// clip out supported child windows
		CRgn rgnClip;
		GetClippingRegion(&rgnClip);
		::ExtSelectClipRgn(hDC, rgnClip, RGN_AND);

		return TRUE;
	}

	// restore old clipping region, only if modified and valid
	if (bUndo && m_nOldClipRgn >= 0)
	{
		if (m_nOldClipRgn == 1)
			::SelectClipRgn(hDC, m_hOldClipRgn);
		else
			::SelectClipRgn(hDC, NULL);
		
		return TRUE;
	}

	return FALSE;
}

/*!
 *  This function is used by this class, and should be used by derived
 *  classes too, in place of the standard GetClientRect. It can be useful
 *  for windows with scrollbars or expanding windows, to provide the true
 *  client area, including even those parts which are not visible.
 *  
 *  @param lpRect Pointer to the RECT structure that holds the result
 *
 *  @remarks Override this function to provide the client area the class uses
 *           to perform layout calculations, both when adding controls and
 *           when rearranging the layout.
 *        @n The base implementation simply calls @c GetClientRect
 */
void CResizableLayout::GetTotalClientRect(LPRECT lpRect) const
{
	GetResizableWnd()->GetClientRect(lpRect);
}

/*!
 *  This function is used to determine if a control needs to be painted when
 *  it is moved or resized by the layout manager.
 *  
 *  @param layout Reference to a @c LAYOUTINFO structure for the control
 *  @param rectOld Reference to a @c RECT structure that holds the control
 *         position and size before the layout update
 *  @param rectNew Reference to a @c RECT structure that holds the control
 *         position and size after the layout update
 *  
 *  @return The return value is @c TRUE if the control should be freshly
 *          painted after a layout update, @c FALSE if not necessary.
 *
 *  @remarks The default implementation tries to identify windows that
 *           need refresh by their class name and window style.
 *        @n Override this function if you need a different behavior or if
 *           you have custom controls that fail to be identified.
 *
 *  @sa LikesClipping InitResizeProperties
 */
BOOL CResizableLayout::NeedsRefresh(const LAYOUTINFO& layout,
								const CRect& rectOld, const CRect& rectNew) const
{
	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 (0 == lstrcmp(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;
		}
		return bRefresh;
	}

	// window classes that don't redraw client area correctly
	// when the hor scroll pos changes due to a resizing
	BOOL bHScroll = FALSE;
	if (0 == lstrcmp(layout.sWndClass, WC_LISTBOX))
		bHScroll = TRUE;

	// fix for horizontally scrollable windows, if wider
	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;
}

/*!
 *  This function is used to determine if a control can be safely clipped
 *  out of the parent window client area when it is repainted, usually
 *  after a resize operation.
 *  
 *  @param layout Reference to a @c LAYOUTINFO structure for the control
 *  
 *  @return The return value is @c TRUE if clipping is supported by the
 *          control, @c FALSE otherwise.
 *
 *  @remarks The default implementation tries to identify @a clippable
 *           windows by their class name and window style.
 *        @n Override this function if you need a different behavior or if
 *           you have custom controls that fail to be identified.
 *
 *  @sa NeedsRefresh InitResizeProperties
 */
BOOL CResizableLayout::LikesClipping(const LAYOUTINFO& layout) const
{
	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 (0 == lstrcmp(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
			// or they could use the registered message (more reliable)
			::GetWindowRect(layout.hWnd, &rect);
			::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
			if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
				WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
				return FALSE;
			break;
		}
		return TRUE;
	}
	else if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
	{
		switch (style & SS_TYPEMASK)
		{
		case SS_LEFT:
		case SS_CENTER:
		case SS_RIGHT:
		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;
}


/*!
 *  @internal This function calculates the new size and position of a
 *  control in the layout and flags for @c SetWindowPos
 */
void CResizableLayout::CalcNewChildPosition(const LAYOUTINFO& layout,
						const CRect &rectParent, CRect &rectChild, UINT& uFlags) const
{
	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.marginTopLeft.cx + rectParent.Width() * layout.anchorTopLeft.cx / 100;
	rectNew.top = layout.marginTopLeft.cy + rectParent.Height() * layout.anchorTopLeft.cy / 100;
	
	// calculate new bottom-right corner
	rectNew.right = layout.marginBottomRight.cx + rectParent.Width() * layout.anchorBottomRight.cx / 100;
	rectNew.bottom = layout.marginBottomRight.cy + rectParent.Height() * layout.anchorBottomRight.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;
}

/*!
 *  This function calculates the top, left, bottom, right margins for a
 *  given size of the specified control.
 *  
 *  @param hWnd Window handle to a control in the layout
 *  @param sizeChild Size of the control to use in calculations
 *  @param rectMargins Holds the calculated margins
 *  
 *  @return The return value is @c TRUE if successful, @c FALSE otherwise
 *
 *  @remarks This function can be used to infer the parent window size
 *           from the size of one of its child controls.
 *           It is used to implement cascading of size constraints.
 */
BOOL CResizableLayout::GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const
{
	POSITION pos;
	if (!m_mapLayout.Lookup(hWnd, pos))
		return FALSE;

	const LAYOUTINFO& layout = m_listLayout.GetAt(pos);

	// augmented size, relative to anchor points
	CSize size = sizeChild + layout.marginTopLeft - layout.marginBottomRight;

	// percent of parent size occupied by this control
	CSize percent(layout.anchorBottomRight.cx - layout.anchorTopLeft.cx,
		layout.anchorBottomRight.cy - layout.anchorTopLeft.cy);

	// calculate total margins
	rectMargins.left = size.cx * layout.anchorTopLeft.cx / percent.cx + layout.marginTopLeft.cx;
	rectMargins.top = size.cy * layout.anchorTopLeft.cy / percent.cy + layout.marginTopLeft.cy;
	rectMargins.right = size.cx * (100 - layout.anchorBottomRight.cx) / percent.cx - layout.marginBottomRight.cx;
	rectMargins.bottom = size.cy * (100 - layout.anchorBottomRight.cy) / percent.cy - layout.marginBottomRight.cy;

	return TRUE;
}

/*!
 *  This function is used to set the initial resize properties of a control
 *  in the layout, that are stored in the @c properties member of the
 *  related @c LAYOUTINFO structure.
 *  
 *  @param layout Reference to the @c LAYOUTINFO structure to be set
 *
 *  @remarks The various flags are used to specify whether the resize
 *           properties (clipping, refresh) can change at run-time, and a new
 *           call to the property querying functions is needed at every
 *           layout update, or they are static properties, and the cached
 *           value is used whenever necessary.
 *        @n The default implementation sends a registered message to the 
 *           control, giving it the opportunity to specify its resize
 *           properties, which takes precedence if the message is supported.
 *           It then sets the @a clipping property as static, calling
 *           @c LikesClipping only once, and the @a refresh property as
 *           dynamic, causing @c NeedsRefresh to be called every time.
 *        @n This should be right for most situations, as the need for
 *           @a refresh usually depends on the size fo a control, while the
 *           support for @a clipping is usually linked to the specific type
 *           of control, which is unlikely to change at run-time, but you can
 *           still override this function if a different beahvior is needed.
 *
 *  @sa LikesClipping NeedsRefresh LAYOUTINFO RESIZEPROPERTIES
 */
void CResizableLayout::InitResizeProperties(LAYOUTINFO &layout) const
{
	// 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;
	}
}

/*!
 *  This function modifies a window to enable resizing functionality.
 *  This affects the window style, size, system menu and appearance.
 *  
 *  @param lpCreateStruct Pointer to a @c CREATESTRUCT structure, usually
 *         passed by the system to the window procedure in a @c WM_CREATE
 *         or @c WM_NCCREATE
 *  
 *  @remarks The function is intended to be called only inside a @c WM_CREATE
 *           or @c WM_NCCREATE message handler.
 */
void CResizableLayout::MakeResizable(LPCREATESTRUCT lpCreateStruct)
{
	if (lpCreateStruct->style & WS_CHILD)
		return;

	CWnd* pWnd = GetResizableWnd();

#if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
	// enable double-buffering on supported platforms
	pWnd->ModifyStyleEx(0, WS_EX_COMPOSITED);
#endif

	if (!(lpCreateStruct->style & WS_THICKFRAME))
	{
		// set resizable style
		pWnd->ModifyStyle(DS_MODALFRAME, WS_THICKFRAME);
		// keep client area
		CRect rect(CPoint(lpCreateStruct->x, lpCreateStruct->y),
			CSize(lpCreateStruct->cx, lpCreateStruct->cy));
		pWnd->SendMessage(WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
		// adjust size to reflect new style
		::AdjustWindowRectEx(&rect, pWnd->GetStyle(),
			::IsMenu(pWnd->GetMenu()->GetSafeHmenu()), pWnd->GetExStyle());
		pWnd->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
			SWP_NOSENDCHANGING|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREPOSITION);
		// update dimensions
		lpCreateStruct->cx = rect.Width();
		lpCreateStruct->cy = rect.Height();
	}
}

/*!
 *  This function should be called inside the parent window @c WM_NCCALCSIZE
 *  message handler to help eliminate flickering.
 *  
 *  @param bAfterDefault Flag that specifies wether the call is made before
 *         or after the default handler
 *  @param lpncsp Pointer to the @c NCCALCSIZE_PARAMS structure that is
 *         passed to the message handler
 *  @param lResult Reference to the result of the message handler.
 *         It contains the default handler result on input and the value to
 *         return from the window procedure on output.
 *  
 *  @remarks This function fixes the annoying flickering effect that is
 *           visible when resizing the top or left edges of the window
 *           (at least on a "left to right" Windows localized version).
 */
void CResizableLayout::HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT &lResult)
{
	// prevent useless complication when size is not changing
	// prevent recursion when resetting the window region (see below)
	if ((lpncsp->lppos->flags & SWP_NOSIZE)
#if (_WIN32_WINNT >= 0x0501)
		|| m_bNoRecursion
#endif
		)
		return;

	if (!bAfterDefault)
	{
		// save a copy before default handler gets called
		m_rectClientBefore = lpncsp->rgrc[2];
	}
	else // after default WM_NCCALCSIZE msg processing
	{
		if (lResult != 0)
		{
			// default handler already uses an advanced validation policy, give up
			return;
		}
		// default calculated client rect
		RECT &rectClientAfter = lpncsp->rgrc[0];

		// intersection between old and new client area is to be preserved
		// set source and destination rects to this intersection
		RECT &rectPreserve = lpncsp->rgrc[1];
		::IntersectRect(&rectPreserve, &rectClientAfter, &m_rectClientBefore);
		lpncsp->rgrc[2] = rectPreserve;

		lResult = WVR_VALIDRECTS;

		// FIX: window region must be updated before the result of the
		//		WM_NCCALCSIZE message gets processed by the system,
		//		otherwise the old window region will clip the client
		//		area during the preservation process.
		//		This is especially evident on WinXP when the non-client
		//		area is rendered with Visual Styles enabled and the
		//		windows have a non rectangular region.
#if (_WIN32_WINNT >= 0x0501)
		//! @todo change rt check to only if themed frame. what about custom wnd region?
		if (real_WIN32_WINNT >= 0x0501)
		{
			CWnd* pWnd = GetResizableWnd();
			DWORD dwStyle = pWnd->GetStyle();
			if ((dwStyle & (WS_CAPTION|WS_MAXIMIZE)) == WS_CAPTION)
			{
				m_bNoRecursion = TRUE;
				pWnd->SetWindowRgn(NULL, FALSE);
				m_bNoRecursion = FALSE;
			}
		}
#endif
	}
}

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paolo Messina
Software Developer RoboTech srl
Italy Italy
Paolo began programming at the age of 9 with a glorious 8086 and GW-BASIC, then he played a bit with C, ASM and Pascal. He tought himself MFC and Windows programming, to exploit his studies of C++. Always attracted by low-level programming and Assembly, he's beginning to appreciate the joys of templates and STL. At work he changed his mind about Java, discovered Eclipse IDE, and now think it's cool.
 
He lives in Follonica, Italy.
 
He has been abroad in the U.S. to work on his final thesis before graduating. For seven months he was playing with airplanes and automatic control at the Unversity of Illinois at Urbana-Champaign.
 
He graduated in Computer Science Engineering at the University of Pisa, Italy, in December 2003.
 
Currently working for an edutainment robotics company (www.robotechsrl.com).
Follow on   Google+

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 15 Jun 2004
Article Copyright 2001 by Paolo Messina
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid