Auto repositioning/resizing of child controls (using C++ templates)

Yet, another method to reposition/resize child windows within their parent framework. Although the subject received a lot of attention, including this web site, I invite you to look at the following method as it presents certain advantages over the others I already know.

First of all this is not a library. There is no associated DLL or LIB to link to. Secondly, it is not a class hierarchy. You dont have to derive your classes from classes exposing this functionality. Often, you already have a hierarchy of classes designed for a certain behavior. This behavior does not include repositioning/resizing of child controls, but you want to add it. Since the MFC doesnt let you use multiple inheritance when it comes to windows, you might wonder what is the solution. Well, the solution is templates. GEOMETRY is actually a collection of templates designed to make your windows to resize gracefully.

The interface to GEOMETRY is a single class template CGeometryWnd. You can use this class template with all your window classes including dialogs, forms, property sheets/property pages and splitter windows. Being a template library, GEOMETRY is contained in a single file Geometry.h. All you have to do is to include this file, to use CGeometryWnd and possibly a series of macros, in relation with your classes.

For example to make a dialog resizeable you need to:

  1. make your dialog in resource editor resizeable (replace the dialog frame with a resizeable border)
  2. specify the rules to apply to child controls (either in the resource editor or in the source code)
  3. include in dialogs source code Geometry.h
  4. use the template CGeometryWnd over your dialogs class

Ex:


#include Geometry.h
USE_GEOMETRY_TEMPLATES;
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
DECLARE_GEOMETRY_RTSUPPORT()

public:
 CAboutDlg();

 // Dialog Data
 //{{AFX_DATA(CAboutDlg)
 enum { IDD = IDD_ABOUTBOX };
 //}}AFX_DATA

 virtual BOOL OnInitDialog();
 //{{AFX_VIRTUAL(CAboutDlg)
 protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
 //}}AFX_VIRTUAL

// Implementation
protected:
 //{{AFX_MSG(CAboutDlg)
 // No message handlers
 //}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
 //{{AFX_DATA_INIT(CAboutDlg)
 //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CAboutDlg)
 //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL CAboutDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 AddConstraint(IDOK,CConstraint("X"));
 return TRUE;
}

// App command to run the dialog
void CTestPSGeoApp::OnAppAbout()
{
 CGeometryWnd<CAboutDlg> aboutDlg;
 aboutDlg.DoModal();
}
The constraints added to child windows, control their behavior in case the parent resizes. The constraints can be added at design time (aka in resource editor, see note) or at runtime using the DECLARE_GEOMETRY_RTSUPPORT() macro and AddConstraint()function. The constraint object has two important constructors:

CConstraint(LPCSTR lpOptions) and
CConstraint(float x,float y=0,float cx=0,float cy=0)
In the first case you can specify the repositioning/resizing of the control by using a control string (see table for details). Basically the string format is:

Option1[value]+Option2[value]+

Where Optionx (see table below) controls the position/size of the control and [value] ([] are not optional) is an optional parameter specifying the amount (between 0 and 1 as a float number) with which the control will reposition/resize from the total amount of the parent resize value.

Option

Meaning

Comment

X

Reposition on x axis

Control will move on the x axis

Y

Reposition on y axis

Control will move on the y axis

CX

Resize on x axis

Control will resize its width

CY

Resize on y axis

Control will resize its height

x

Stay on x axis center

Equivalent with X[0.5]

y

Stay on y axis center

Equivalent with Y[0.5]

cx

 

Equivalent with CX[0.5]

cy

 

Equivalent with CY[0.5]

MX

Maintain aspect on x axis

Preserves the X/CXParent ratio

MY

Maintain aspect on y axis

Preserves the Y/CYParent ratio

MCX

Maintain aspect on the width

Preserves the CX/CXParent ratio

MCY

Maintain aspect on the height

Preserves the CY/CYParent ratio

Note:

To add the constraints at design time directly in resource editor you ll gonna surround the controls sharing the same constraint options with invisible group boxes having the caption in the form " $Geometry:Option1[value]+Option2[value]+" w/o the quotes. Please note the $Geometry in front of the options string, this is not optional. Be careful with the capital/minor letters as they have different significance. Using this form of constraints specification you dont need DECLARE_GEOMETRY_RTSUPPORT() and AddConstraint() as previously displayed.

Using the second constructor you will specify the values between 0 and 1 that will be multiplied with the total resize amount of the parent. A value of 0 means dont move/resize a value of 1 means full movement/resizing. Please note that the values of movement/resizing on one axis added together must not be greater than 1 (otherwise the control will be clipped away from its parent).

AddConstraint(UINT nIDC,CConstraint& constraint) takes as parameters the control ID and a reference to a constraint object.

To achieve dynamic creation of windows MFC uses DECLARE_DYNCREATE(), IMPLEMENT_DYNCREATE() along with RUNTIME_CLASS() macros. For example the CFormView derived classes are usually created by the framework in this way. To accommodate this case GEOMETRY uses a set of macros of its own:


DECLARE_GEOMETRY_DYNCREATE(yourClass)
IMPLEMENT_GEOMETRY_DYNCREATE(yourClass)
GEOMETRY_RUNTIME_CLASS(yourClass)

The first macro is to be used in the definition of the class (however outside the class scope), the second in the implementation of the class and eventually the third instead of RUNTIME_CLASS(). The first two dont replace DECLARE_DYNCREATE(), IMPLEMENT_DYNCREATE() macros, they still have to be used.

For example:


#include <Geometry.h>
USE_GEOMETRY_TEMPLATES;
class CTestPSGeoView : public CFormView
{
 protected: // create from serialization only
 CTestPSGeoView();
 DECLARE_DYNCREATE(CTestPSGeoView)
 
};

DECLARE_GEOMETRY_DYNCREATE(CTestPSGeoView);
In the definition file of the CTestPSGeoView class and

IMPLEMENT_DYNCREATE(CTestPSGeoView,CFormView)
IMPLEMENT_GEOMETRY_DYNCREATE(CTestPSGeoView)

In the implementation file.

Also, there is necessary the substitution:


CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TESTPSTYPE,
RUNTIME_CLASS(CTestPSGeoDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
GEOMETRY_RUNTIME_CLASS(CTestPSGeoView));

Thats it! An example along with the full source code is provided. You may use freely this template library, even modify it, but at your own risk and specifying where the code comes from. The template library was built and tested on VC 6.0 only.

Download demo project - 36 KB

Date Last Updated: February 4, 1999



Comments

  • Geometry with CustomDraw

    Posted by Dontsov Evgeny on 07/05/2012 02:49am

    Thanks Frank J. Lagattuta! It's really problem with coloring CListCtrl with using Geometry in Dialog! NM_CUSTOMDRAW doesn't work cristal! to get CDDS_ITEMPREPAINT you should use Frank J. Lagattuta OnNotify function!

    Reply
  • How to use Geometry for Composite ATL controls ?

    Posted by Legacy on 02/28/2003 12:00am

    Originally posted by: smd

    Need help on how to use the Geometry templates in Composite
    ATL controls used in MFC applications. For example a Composite ATL control has couple of edit controls and comboboxes. Need only to resize Edit controls.

    Reply
  • Fix to allow for programmatic resizing.

    Posted by Legacy on 12/05/2002 12:00am

    Originally posted by: Trey Van Riper

    Motivation:
    
    

    I wanted to be able to allow the user to save the resized
    window dimensions, but unfortunately I could not initialize
    the window to the new size without experiencing some
    amusing problems with the controls. That is, the controls
    would be one size, while the window would be the original
    size of the resource.

    So, I modified and exposed the GeometryInitialize()
    function to give me a way around the problem.

    The new GeometryInitialized() function now looks like this:

    template <typename T> inline void CGeometryWnd<T>::GeometryInitialize()
    {
    static bool is_initialized = false;
    if ( !is_initialized ) {
    T* pThis = static_cast<T*>(this);

    pThis->GetClientRect(m_minRect);
    pThis->GetClientRect(m_originalRect);

    BuildGeometryList();
    m_cList.SetRects(pThis->GetSafeHwnd());
    is_initialized = true;
    }
    }

    And, to expose this, I modified the
    DECLARE_GEOMETRY_RTSUPPORT() macro as follows:

    #define DECLARE_GEOMETRY_RTSUPPORT() \
    public: \
    bool AddConstraint(UINT nID,CConstraint& cstr) { return AddConstraint(nID,cstr,this); };\
    virtual bool AddConstraint(UINT nID,CConstraint& cstr,CWnd* pParent) { ASSERT(FALSE); return true; };\
    virtual void RemoveConstraint(UINT nID) { ASSERT(FALSE); }; \
    virtual void GeometryInitialize() { ASSERT(FALSE); };

    So now, I'm able to do something like this:

    BOOL MyDialog::OnInitDialog()
    {
    CDialog::OnInitDialog();
    AddConstraint( IDC_SPIFFY_CONTROL, CConstraint("CX") );
    GeometryInitialize();
    ReadAndSetDialogSizeFromRegistry();
    }

    I hope this is useful to someone.

    Reply
  • Inserting & resizing ActiveX controls

    Posted by Legacy on 04/11/2001 12:00am

    Originally posted by: Michael

    To be able to resize ActiveX one should chande two subroutines in the Geometry.h:
    
    

    inline void CBindCtlToConstraint::ResizeControl(HWND hwndParent,CSize pExt,CSize mExt)
    {
    // changes 11.4.2001
    CRect rCtl;
    CWnd* pParent = CWnd::FromHandle(hwndParent);
    HWND hwnd = NULL;//GetDlgItem(hwndParent,m_rsctl.m_nID);
    CWnd *pwc=NULL;
    if(pParent) {
    pwc = pParent->GetDlgItem(m_rsctl.m_nID);
    if(pwc) hwnd= pwc->GetSafeHwnd();
    }
    ASSERT(hwnd != NULL);

    if(pwc) pwc->GetWindowRect(rCtl);
    ///else ::GetWindowRect(hwnd,rCtl);
    pParent->ScreenToClient(rCtl);

    // pExt is the new size of the parent rect
    // mExt is the initial size of the parent rect
    int x = m_rsctl.m_iRect.left;
    int y = m_rsctl.m_iRect.top;
    int cx = m_rsctl.m_iRect.Width();
    int cy = m_rsctl.m_iRect.Height(); // the new coordinates of the control rect

    if (m_constraint.m_options & CConstraint::eRatioX)
    x = pExt.cx*m_rsctl.m_iRect.left/mExt.cx;
    if (m_constraint.m_options & CConstraint::eRatioCX)
    cx = pExt.cx*m_rsctl.m_iRect.Width()/mExt.cx;
    if (m_constraint.m_options & CConstraint::eRatioY)
    y = pExt.cy*m_rsctl.m_iRect.top/mExt.cy;
    if (m_constraint.m_options & CConstraint::eRatioCY)
    cy = pExt.cy*m_rsctl.m_iRect.Height()/mExt.cy;
    if (m_constraint.m_options & CConstraint::eCenterX)
    x += (pExt.cx-mExt.cx)/2;
    if (m_constraint.m_options & CConstraint::eCenterY)
    y += (pExt.cy-mExt.cy)/2;
    if (m_constraint.m_options & CConstraint::eResizeCX)
    cx += (int)((pExt.cx-mExt.cx) * m_constraint.m_cxratio);
    if (m_constraint.m_options & CConstraint::eResizeCY)
    cy += (int)((pExt.cy-mExt.cy) * m_constraint.m_cyratio);
    if (m_constraint.m_options & CConstraint::eMoveX)
    x += (int)((pExt.cx-mExt.cx) * m_constraint.m_xratio);
    if (m_constraint.m_options & CConstraint::eMoveY)
    y += (int)((pExt.cy-mExt.cy) * m_constraint.m_yratio);

    if (x < m_rsctl.m_iRect.left)
    x = m_rsctl.m_iRect.left;
    if (y < m_rsctl.m_iRect.top)
    y = m_rsctl.m_iRect.top;
    if (cx < m_rsctl.m_iRect.Width())
    cx = m_rsctl.m_iRect.Width();
    if (cy < m_rsctl.m_iRect.Height())
    cy = m_rsctl.m_iRect.Height();

    UINT nFlags = SWP_NOZORDER | ((GetWindowLong(hwnd,GWL_STYLE) & WS_VISIBLE)? SWP_SHOWWINDOW : 0);
    //ShowWindow(hwnd,SW_HIDE);

    if(pwc) pwc->SetWindowPos(NULL,x,y,cx,cy,nFlags); //pwc->MoveWindow(x,y,cx,cy);
    ////else SetWindowPos(hwnd,NULL,x,y,cx,cy,nFlags);
    // InvalidateRects(pParent,rCtl,CRect(x,y,x+cx,y+cy));
    }

    inline void CResizeCtlList::SetRects(HWND hParent)
    {
    CRect rect;
    POSITION pos = m_gCtl.GetHeadPosition();

    while (pos)
    {
    CBindCtlToConstraint* pcbc = m_gCtl.GetNext(pos);
    HWND hCtl = GetDlgItem(hParent,pcbc->m_rsctl.m_nID);

    //////////////////inserted //////////////
    CWnd* pParent = CWnd::FromHandle(hParent);
    CWnd *pwc=NULL;
    if(pParent) {
    pwc = pParent->GetDlgItem(pcbc->m_rsctl.m_nID);
    if(pwc) hCtl= pwc->GetSafeHwnd();
    }
    if(pwc) pwc->GetWindowRect(rect);
    //////////////////////////////////////////

    CWnd::FromHandle(hParent)->ScreenToClient(rect);
    pcbc->SetRect(rect);
    }
    }

    Reply
  • How to use a combobox in msflexgrid in vc++

    Posted by Legacy on 04/04/2001 12:00am

    Originally posted by: Mahesh Kumar

    How to put a combobox for a particular column in msflexgrid in vc++

    Reply
  • How to use a combobox in msflexgrid in vc++

    Posted by Legacy on 04/04/2001 12:00am

    Originally posted by: Mahesh Kumar

    How to put a combobox for a particular column in msflexgrid in vc++

    Reply
  • ASSERT() fails when resizing Wang Image Edit ActiveX Control

    Posted by Legacy on 08/15/2000 12:00am

    Originally posted by: Philip Chang

    Thanks for a very elegant and easy to use solution.

    However, when I try to AddConstraint() for a Wang Image Edit ActiveX Control inserted via resource editor, I would get the ASSERT() failure in geometry.h, line 422.

    Please let me know how I can find out what I am doing wrong.

    Thanks!
    Philip Chang

    Reply
  • Unable to reduce the dialog size less than Initial size of dialog created using Resource editor.

    Posted by Legacy on 02/09/2000 12:00am

    Originally posted by: Srinivas Vaddi

    Tryed to reduce and resize dialog layout but i am not able to achive the reduced size.
    Code works perfectly when Trying to enlarge the size of the dialog & Controls.
    Please suggest....
    VSReddy.

    Reply
  • Resizing

    Posted by Legacy on 12/16/1999 12:00am

    Originally posted by: Prasanna

    Can you please explain me what is the creteria of deciding the ratio in the following lineBOOL CAddedPage2::OnInitDialog()
    {
    CPropertyPage::OnInitDialog();

    AddConstraint(IDOK,CConstraint(1));
    AddConstraint(IDCANCEL,CConstraint(1));
    AddConstraint(IDC_EDIT1,CConstraint(0,0,1,0));
    AddConstraint(IDC_EDIT2,CConstraint(0,0,1,0));
    AddConstraint(IDC_LIST1,CConstraint(0,0,1,0.5));
    AddConstraint(IDC_LIST2,CConstraint(0,0.5,1,0.5));
    }

    Reply
  • Problem with OnNotify Override in template class

    Posted by Legacy on 10/03/1999 12:00am

    Originally posted by: Frank J. Lagattuta

    Great code!  I use it with a CFormView derived class of
    
    mine - repositions/resizes controls, works great!

    However, I ran into a problem. In my FormView I have
    list ctrls and tree ctrls that allow for in-place editing
    for subitems using dropdown list ctrl, spin ctrls, etc.
    I have implemented this as described in other sections of
    CodeGuru.

    Basically, the list/tree ctrls use message reflection to
    handle the BeginLabelEdit and EndLabelEdit notifications
    and in their handling create an in-place ctrl of the proper
    type, give it focus, and let the user edit. When the
    in-place edit ctrl loses focus, it destroys itself and
    sends an EndLabelEdit message back to the list/tree ctrl.

    In geometry.h there is an override of the CWnd::OnNotify
    function that looks like this:

    template <class T> BOOL ACtrlGeometryWnd<T>::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
    NMHDR* pNMHDR = (NMHDR*)lParam;
    CRect rWnd;

    T::OnNotify(wParam,lParam,pResult);

    // don't handle messages not from the page/sheet itself
    if (pNMHDR->hwndFrom != m_hWnd && pNMHDR->hwndFrom != ::GetParent(m_hWnd))
    return FALSE;

    if (pNMHDR->code == PSN_SETACTIVE)
    {
    GetWindowRect(rWnd);
    SetWindowPos(NULL,0,0,rWnd.Width(),rWnd.Height(),SWP_NOZORDER|SWP_NOMOVE);
    }

    *pResult = 0;
    return TRUE;
    }

    This breaks the notification chain because, even though it
    calls the base class OnNotify functionality, it ignores
    the value set in pResult and always returns TRUE.

    In addition, it checks to see where the notification came
    from and ignores it if it is not from the "page/sheet
    itself". I don't know why you would want to do this.

    To fix I changed this function to:

    template <class T> BOOL ACtrlGeometryWnd<T>::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
    NMHDR* pNMHDR = (NMHDR*)lParam;
    CRect rWnd;
    BOOL bRetVal;

    bRetVal = T::OnNotify(wParam,lParam,pResult);

    if (pNMHDR->code == PSN_SETACTIVE)
    {
    GetWindowRect(rWnd);
    SetWindowPos(NULL,0,0,rWnd.Width(),rWnd.Height(),SWP_NOZORDER|SWP_NOMOVE);
    }

    return bRetVal;
    }

    This preserves the base class values for the return value
    and pResult and doesn't simply return FALSE if the message
    is not from the page/sheet.

    All repositioning/resizig works as before and my custom
    in-place edit ctrls work also.

    -Frank

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds