// cdxCDynamicWnd.cpp: implementation of the cdxCDynamicWnd class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "cdxCDynamicWnd.h"
#include "cdxCSizeIconCtrl.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#pragma warning(disable: 4100)
#pragma warning(disable: 4706)
IMPLEMENT_DYNAMIC(cdxCDynamicLayoutInfo,CObject);
//////////////////////////////////////////////////////////////////////
// cdxCDynamicWnd::Position
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Positioning engine
//////////////////////////////////////////////////////////////////////
/*
* Standard Controller's Position() routine
* This has the same functionality as known from the former
* cdxCDynamicControlsManager class.
*
* One exception is the new "szMin" property which allows
* the class to "hide" the control if it becomes too small
* (it will be moved outside the client area).
*/
void cdxCDynamicWnd::Position::Apply(HWND hwnd, CRect & rectNewPos, const cdxCDynamicLayoutInfo & li) const
{
if(li.m_bUseScrollPos)
{
rectNewPos.left = left - li.m_pntScrollPos.x;
rectNewPos.right = right - li.m_pntScrollPos.x;
rectNewPos.top = top - li.m_pntScrollPos.y;
rectNewPos.bottom = bottom - li.m_pntScrollPos.y;
if(li.m_szDelta.cx >= 0)
{
rectNewPos.left += (m_Bytes[X1] * li.m_szDelta.cx) / 100;
rectNewPos.right += (m_Bytes[X2] * li.m_szDelta.cx) / 100;
}
if(li.m_szDelta.cy >= 0)
{
rectNewPos.top += (m_Bytes[Y1] * li.m_szDelta.cy) / 100;
rectNewPos.bottom += (m_Bytes[Y2] * li.m_szDelta.cy) / 100;
}
}
else
{
rectNewPos.left = left + (m_Bytes[X1] * li.m_szDelta.cx) / 100;
rectNewPos.right = right + (m_Bytes[X2] * li.m_szDelta.cx) / 100;
rectNewPos.top = top + (m_Bytes[Y1] * li.m_szDelta.cy) / 100;
rectNewPos.bottom = bottom + (m_Bytes[Y2] * li.m_szDelta.cy) / 100;
}
if(rectNewPos.left + m_szMin.cx >= rectNewPos.right)
{
rectNewPos.right = -0;
rectNewPos.left = rectNewPos.right - m_szMin.cx;
}
if(rectNewPos.top + m_szMin.cy >= rectNewPos.bottom)
{
rectNewPos.bottom = -0;
rectNewPos.top = rectNewPos.bottom - m_szMin.cy;
}
}
//////////////////////////////////////////////////////////////////////
// cdxCDynamicWnd
//////////////////////////////////////////////////////////////////////
const CSize cdxCDynamicWnd::M_szNull(0,0);
const cdxCDynamicWnd::SBYTES cdxCDynamicWnd::TopLeft = { 0,0,0,0 },
cdxCDynamicWnd::TopRight = { 100,0,100,0 },
cdxCDynamicWnd::BotLeft = { 0,100,0,100 },
cdxCDynamicWnd::BotRight = { 100,100,100,100 };
//////////////////////////////////////////////////////////////////////
// construction
//////////////////////////////////////////////////////////////////////
/*
* construction
*/
cdxCDynamicWnd::cdxCDynamicWnd(Freedom fd, UINT nFlags)
: m_pWnd(NULL),
m_iDisabled(0),
m_Freedom(fd),
m_szInitial(M_szNull),
m_szMin(0,0),
m_szMax(0,0),
m_bUseScrollPos(false),
m_pSizeIcon(NULL),
m_idSizeIcon(AFX_IDW_SIZE_BOX),
m_nMyTimerID(0),
m_nFlags(nFlags)
{
}
//////////////////////////////////////////////////////////////////////
// control work
//////////////////////////////////////////////////////////////////////
/*
* AddSzControl()
* --------------
* Add a control that will react on changes to the parent window's size.
* hwnd - the child control.
* pos - describes what to do at all.
* bReposNow - true to immediately make the control change its position
* if necessary, false if not
* In the latter case you may like to call Layout() afterwards.
*
* returns false if an invalid window has been passed to this funciton.
*/
bool cdxCDynamicWnd::AddSzControl(HWND hwnd, const Position & pos, bool bReposNow)
{
if(!IsWindow())
{
ASSERT(IsWindow());
return false; // NO assert if hwnd is invalid
}
if(!::IsWindow(hwnd))
{
TRACE(_T("*** NOTE[cdxCDynamicWnd::AddSzControl(HWND,const Position &,bool)]: Handle 0x%lx is not a valid window.\n"),(DWORD)hwnd);
return false;
}
m_Map.SetAt(hwnd,pos);
if(bReposNow)
UpdateControlPosition(hwnd);
return true;
}
/*
* AllControls()
* -------------
* Apply positioning to all controls of the window.
* bytes - positioning data
* bOverwrite - overwrite any existing positioning data.
* bReposNow - true to immediately make the control change its position
* if necessary, false if not
* In the latter case you may like to call Layout() afterwards.
*/
void cdxCDynamicWnd::AllControls(const SBYTES & bytes, bool bOverwrite, bool bReposNow)
{
if(!IsWindow())
{
ASSERT(false);
return;
}
Position pos;
UINT nCnt = 0;
for(HWND hwnd = ::GetWindow(m_pWnd->m_hWnd,GW_CHILD); hwnd; hwnd = ::GetNextWindow(hwnd,GW_HWNDNEXT))
{
if(bOverwrite || !m_Map.Lookup(hwnd,pos))
if(AddSzControl(hwnd,bytes,false))
++nCnt;
}
if(nCnt && bReposNow)
Layout();
}
/*
* RemSzControl()
* --------------
* Removes a control from the internal list.
* The control will remain at its initial position if bMoveToInitialPos is false
* Returns false if an error occured.
*/
bool cdxCDynamicWnd::RemSzControl(HWND hwnd, bool bMoveToInitialPos)
{
if(!::IsWindow(hwnd) || !IsWindow())
return false;
if(bMoveToInitialPos)
{
Position pos;
if(!m_Map.Lookup(hwnd,pos))
return false;
VERIFY( ::SetWindowPos(hwnd,HWND_TOP,
pos.left,pos.top,pos.Width(),pos.Height(),
SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER) );
}
return m_Map.RemoveKey(hwnd) != FALSE;
}
/*
* UpdateControlPosition()
* =======================
* Move control to its desired position.
* returns false if HWND is not valid.
*/
bool cdxCDynamicWnd::UpdateControlPosition(HWND hwnd)
{
if(!IsWindow())
{
ASSERT(IsWindow());
return false; // NO assert if hwnd is invalid
}
if(!::IsWindow(hwnd))
{
TRACE(_T("*** NOTE[cdxCDynamicWnd::UpdateControlPosition()]: Handle 0x%lx is not a valid window.\n"),(DWORD)hwnd);
return false;
}
cdxCDynamicLayoutInfo *pli = DoCreateLayoutInfo();
ASSERT(pli != NULL);
if(!pli || !pli->IsInitial())
{
try
{
CRect rectNew;
WINDOWPLACEMENT wpl;
wpl.length = sizeof(WINDOWPLACEMENT);
VERIFY(::GetWindowPlacement(hwnd,&wpl) );
rectNew = wpl.rcNormalPosition;
if(DoMoveCtrl(hwnd,::GetDlgCtrlID(hwnd),rectNew,*pli) &&
(rectNew != wpl.rcNormalPosition) )
{
VERIFY( ::SetWindowPos(hwnd,HWND_TOP,
rectNew.left,rectNew.top,rectNew.Width(),rectNew.Height(),
SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER) );
}
}
catch(...)
{
delete pli;
throw;
}
}
delete pli;
return true;
}
//////////////////////////////////////////////////////////////////////
// main layout engine
//////////////////////////////////////////////////////////////////////
/*
* Layout()
* --------
* Iterates through all child windows and calls DoMoveCtrl() for them.
* This function is NOT virtual.
* To implement your own layout algorithm, please
* a) overwrite DoCreateLayoutInfo() to return an object of a class
* derived from cdxCDynamicLayoutInfo.
* You can put any user-data into your object; it will be passed
* on to the DoMoveCtrl() function.
* b) overwrite DoMoveCtrl() and implement the layout logic.
* An example can be found in the example project, anytime.
*/
void cdxCDynamicWnd::Layout()
{
if(!IsWindow())
{
ASSERT(IsWindow());
return;
}
// resize stuff
cdxCDynamicLayoutInfo *pli = DoCreateLayoutInfo();
if(!pli)
{
ASSERT(false); // YOU MUST PROVIDE A LAYOUT INFO BLOCK !
return;
}
try
{
HDWP hdwp = ::BeginDeferWindowPos(pli->m_nCtrlCnt);
HWND hwnd;
bool bRepeat;
CRect rectNew;
UINT id;
WINDOWPLACEMENT wpl;
DWORD swpFlags = SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER|(!(m_nFlags & flSWPCopyBits) ? SWP_NOCOPYBITS : 0);
if(!( hwnd = ::GetWindow(m_pWnd->m_hWnd,GW_CHILD) ))
{
TRACE(_T("*** NOTE[cdxCDynamicWnd::Layout()]: The window at 0x%lx does not have child windows.\n"),(DWORD)m_pWnd->m_hWnd);
return;
}
do
{
bRepeat = false;
for(; hwnd; hwnd = ::GetNextWindow(hwnd,GW_HWNDNEXT))
{
wpl.length = sizeof(WINDOWPLACEMENT);
if(!::GetWindowPlacement(hwnd,&wpl))
{
ASSERT(false); // GetWindowPlacement() failed
continue;
}
rectNew = wpl.rcNormalPosition;
ASSERT(rectNew.left >= 0);
id = ::GetDlgCtrlID(hwnd);
if(!DoMoveCtrl(hwnd,id,rectNew,*pli) ||
(rectNew == wpl.rcNormalPosition) )
{
// window doesn't need to be moved
// (position is not been changed)
continue;
}
if(hdwp)
{
if(!( hdwp = ::DeferWindowPos(hdwp,hwnd,HWND_TOP,
rectNew.left,rectNew.top,rectNew.Width(),rectNew.Height(),
swpFlags) ))
{
TRACE(_T("*** ERROR[cdxCDynamicWnd::ReorganizeControls()]: DeferWindowPos() failed ??\n"));
bRepeat = true;
break; // error; we'll repeat the loop by using SetWindwPos()
// this won't look good, but work :)
}
}
else
{
VERIFY( ::SetWindowPos(hwnd,HWND_TOP,
rectNew.left,rectNew.top,rectNew.Width(),rectNew.Height(),
swpFlags) );
}
}
}
while(bRepeat);
if(hdwp)
{
VERIFY( ::EndDeferWindowPos(hdwp) );
}
}
catch(...)
{
delete pli;
throw;
}
delete pli;
}
//////////////////////////////////////////////////////////////////////
// message work
//////////////////////////////////////////////////////////////////////
/*
* DoMoveCtrl()
* ------------
* This virtual function is used to calculate a child window's new position
* based on the some data (from the cdxCDynamicLayoutInfo object).
* This standard routine is made to implement the algorithm as known from
* the cdxCDynamicControlsManager.
* You can implement your own code if you are not satisfied with the
* following function.
* If you need global data, overwrite DoCreateLayoutInfo() which will
* be called by Layout() and which you can use to collect these data
* once for the entire layout process.
*
* PARAMETERS:
*
* hwnd - handle of the child control
* id - its id
* rectNewPos - write the new position here in.
* initially contains the current position
* li - Some information on the parent window.
* You can provide extra information here
* by overwriting DoCreateLayoutInfo().
*
* RETURN CODES:
*
* return false if you don't want to move the control
* return true if you updated the control's position and stored it into "rectNewPos"
* If you don't change it, the control will not be moved.
*
* #### don't move the control by yourself. Layout() will do for you to ensure
* that as little flickering as possible will occur.
*/
bool cdxCDynamicWnd::DoMoveCtrl(HWND hwnd, UINT id, CRect & rectNewPos, const cdxCDynamicLayoutInfo & li)
{
Position pos;
if(!GetControlPosition(hwnd,pos))
return false;
pos.Apply(hwnd,rectNewPos,li);
return true;
}
/*
* DoDestroyCtrl()
* ---------------
* Called when a child window is about being destroyed.
* We use it to remove our "Position" data from our database.
*/
void cdxCDynamicWnd::DoDestroyCtrl(HWND hwnd)
{
m_Map.RemoveKey(hwnd);
}
//////////////////////////////////////////////////////////////////////
// initialization & clean-uo
//////////////////////////////////////////////////////////////////////
/*
* DoInitWindow()
* --------------
* This function sets up the window pointer.
* It is recommended that "rWnd" points to an existing CWnd.
* However, it doesn't need to exist as long as you
* 1) provide a non-zero "szInitial" object.
* 2) don't want a size icon.
*
* PARAMETERS:
*
* rWnd - reference to your window ("*this")
* the window must exist (::IsWindow(rWnd.m_hWnd) must be true)
* fd - Freedom (in which direction(s) your window shall be sizable BY THE USER):
* Possible values: fdAll, fdHorz, fdVert and fdNone.
* This is only applied to user-actions; resizing + layout may work
* even if the freedom parameter is fdNone (in that case user cannot resize
* your window, but you can).
* flags - several flags:
* flSizeIcon - creates a size icon
* flAntiFlicker - activates anti-flickering stuff
* [szInitial - initial client size]
*/
void cdxCDynamicWnd::DoInitWindow(CWnd & rWnd)
{
ASSERT(::IsWindow(rWnd.m_hWnd)); // ensure the window exists ...
m_pWnd = &rWnd;
DoInitWindow(rWnd,GetCurrentClientSize());
}
void cdxCDynamicWnd::DoInitWindow(CWnd & rWnd, const CSize & szInitial)
{
ASSERT(::IsWindow(rWnd.m_hWnd) && szInitial.cx && szInitial.cy); // ensure the window exists ...
m_pWnd = &rWnd;
m_szInitial = szInitial;
m_szMin = szInitial;
/*
* this window will flicker somewhat deadly if you do not have the
* WS_CLIPCHILDREN style set for you window.
* You may like to use the following line anywhere
* to apply it:
CWnd::ModifyStyle(0,WS_CLIPCHILDREN);
*/
#ifdef _DEBUG
if(!(rWnd.GetStyle() & WS_CLIPCHILDREN) && !(m_nFlags & flSWPCopyBits))
{
TRACE(_T("***\n"
"*** cdxCDynamicWnd class note: If your window flickers too much, add the WS_CLIPCHILDREN style to it\n"
"*** or try to set the flSWPCopyBits flags !!!\n"
"***\n"));
}
#endif
//
// now, if a DYNAMIC MAP is been defined,
// we start working with it
//
const __dynEntry *pEntry,*pLast = NULL;
UINT nInitCnt = GetCtrlCount();
if(pLast = __getDynMap(pLast))
{
HWND hwnd;
SBYTES bytes;
for(pEntry = pLast; pEntry->type != __end; ++pEntry)
{
if((pEntry->id != DYNAMIC_MAP_DEFAULT_ID)
&& !( hwnd = ::GetDlgItem(m_pWnd->m_hWnd,pEntry->id) ))
{
TRACE(_T("*** NOTE[cdxCDynamicWnd::DoInitWindow()]: Dynamic map initialization: There's no control with the id 0x%lx !\n"),pEntry->id);
continue;
}
switch(pEntry->type)
{
case __bytes:
bytes[X1] = pEntry->b1;
bytes[Y1] = pEntry->b2;
bytes[X2] = pEntry->b3;
bytes[Y2] = pEntry->b4;
break;
case __modes:
_translate((Mode)pEntry->b1,bytes[X1],bytes[X2]);
_translate((Mode)pEntry->b2,bytes[Y1],bytes[Y2]);
break;
default:
ASSERT(false); // never come here !!!!!
break;
}
if(pEntry->id == DYNAMIC_MAP_DEFAULT_ID)
AllControls(bytes,false,false);
else
AddSzControl(hwnd,bytes,M_szNull,false);
}
}
//
// handle creation flags
//
if(m_nFlags & flSizeIcon)
{
m_pSizeIcon = new cdxCSizeIconCtrl;
VERIFY( m_pSizeIcon->Create(m_pWnd) );
AddSzControl(m_pSizeIcon->m_hWnd,BotRight,M_szNull,false);
m_pSizeIcon->ShowWindow(SW_SHOW);
}
m_bIsAntiFlickering = false;
m_nMyTimerID = DEFAULT_TIMER_ID;
m_dwClassStyle = ::GetClassLong(*m_pWnd,GCL_STYLE) & (CS_VREDRAW|CS_HREDRAW);
OnInitialized();
if(nInitCnt < GetCtrlCount())
Layout();
}
/*
* DoDestroyWindow()
* -----------------
* Clean up.
*/
void cdxCDynamicWnd::DoOnDestroy()
{
if(IsWindow())
OnDestroying();
m_iDisabled = 1;
m_pWnd = NULL;
m_Map.RemoveAll();
if(m_pSizeIcon)
{
m_pSizeIcon->DestroyWindow();
delete m_pSizeIcon;
m_pSizeIcon = NULL;
}
}
//////////////////////////////////////////////////////////////////////
// message work
//////////////////////////////////////////////////////////////////////
/*
* DoOnSize()
* ----------
* Calls Layout() if necessary.
*/
void cdxCDynamicWnd::DoOnSize(UINT nType, int cx, int cy)
{
if(!IsDisabled() &&
IsWindow() &&
(nType != SIZE_MINIMIZED))
{
Layout();
}
}
/*
* DoOnSizing()
* ------------
* This is my turbo-new-super-duper anti-flickering function
* StartAntiFlickering() is called by the following handler.
*/
void cdxCDynamicWnd::DoOnSizing(UINT fwSide, LPRECT pRect)
{
if(m_nMyTimerID && !IsDisabled() && IsWindow() && (m_nFlags & flAntiFlicker))
StartAntiFlickering( (fwSide == WMSZ_BOTTOM) ||
(fwSide == WMSZ_BOTTOMRIGHT) ||
(fwSide == WMSZ_RIGHT));
}
/*
* StartAntiFlickering()
* ---------------------
* This routine modifies the CS_VREDRAW and CS_HREDRAW CLASS style
* flags.
* If you don't like this, set "m_nMyTimerID" to 0.
* bIsBotRight - true if the window is sized in right, bottom or bot/right direction.
*/
void cdxCDynamicWnd::StartAntiFlickering(bool bIsBotRight)
{
if(IsWindow() && m_nMyTimerID)
{
DWORD dw = m_dwClassStyle;
if(bIsBotRight)
dw &= ~(CS_VREDRAW|CS_HREDRAW);
else
dw |= CS_VREDRAW|CS_HREDRAW;
m_pWnd->KillTimer(m_nMyTimerID);
m_pWnd->SetTimer(m_nMyTimerID,120,NULL);
if(!m_bIsAntiFlickering)
{
::SetClassLong(*m_pWnd,GCL_STYLE,dw);
m_bIsAntiFlickering = true;
}
}
}
/*
* DoOnTimer()
* -----------
* Processes the timer associated to my DoOnSizing() routine.
* Changes back the class style.
*/
void cdxCDynamicWnd::DoOnTimer(UINT nIDEvent)
{
if(IsWindow() && (nIDEvent == m_nMyTimerID))
{
m_pWnd->KillTimer(m_nMyTimerID);
if(m_bIsAntiFlickering)
{
::SetClassLong(*m_pWnd,GCL_STYLE,m_dwClassStyle);
m_bIsAntiFlickering = false;
}
}
}
//////////////////////////////////////////////////////////////////////
/*
* DoOnGetMinMaxInfo()
* -------------------
* fill in MINMAXINFO as requested
* Call your CWnd's OnGetMinMaxInfo first !
* [changed due to a bug reported by Michel Wassink <mww@mitutoyo.nl>]
*/
void cdxCDynamicWnd::DoOnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
if(IsWindow() && !IsDisabled())
{
CSize szDelta = GetBorderSize();
lpMMI->ptMinTrackSize.x = m_szMin.cx + szDelta.cx;
lpMMI->ptMinTrackSize.y = m_szMin.cy + szDelta.cy;
if(m_Freedom & fdHoriz)
{
if(m_szMax.cx > 0)
lpMMI->ptMaxTrackSize.x = m_szMax.cx + szDelta.cx;
}
else
lpMMI->ptMaxTrackSize.x = lpMMI->ptMinTrackSize.x;
if(m_Freedom & fdVert)
{
if(m_szMax.cy > 0)
lpMMI->ptMaxTrackSize.y = m_szMax.cy + szDelta.cy;
}
else
lpMMI->ptMaxTrackSize.y = lpMMI->ptMinTrackSize.y;
}
}
//////////////////////////////////////////////////////////////////////
/*
* DoOnParentNotify()
* ------------------
* When a child window is been destroyed, we remove the appropiate
* HWND entries.
*/
void cdxCDynamicWnd::DoOnParentNotify(UINT message, LPARAM lParam)
{
if(!lParam || (message != WM_DESTROY))
return;
DoDestroyCtrl((HWND)lParam);
}