A Simple Dialog Layout Manager

Thanks to Marc Flerackers for updating the Article.


If you often use Dialogs and want to resize them you will have noticed, that there is no feature in the MFC that helps you. You have to do it by hand. Maybe you have seen the GridBagLayout in Java(tm), then this one will look familiar to you. The idea behind is to split the dialog in Panes. Each pane has some attributes an can also contain Panes itself. Resizing your Dialog can now be as easy as requesting the root pane to fit in the current client area! Actually this is done automatically in the OnSize handler, so you just have to define the layout.

Instructions:

  1. Add DialogMgr.cpp to your current project (e.g. Menu Project->Add to project->Add files)
  2. Create a new dialog or skip to the next point if you already have one. I assume this class to be named CMyDialog. (Note: Do not forget to make the Dialog resizable, i.e. in the Dialog Editor change the border style to resizing)
  3. Change the Base of your Dialog from CDialog to CDialogMgr (simple search for all references to CDialog and replace them), do not forget to include the header.
  4. In your class Declaration add a DECLARE_LAYOUT like this:
    
    class CMyDialog : public CDialogMgr
    {
    public:
    	CMyDialog(CWnd* pParent = NULL);   // standard constructor
    	DECLARE_LAYOUT();
    
    	[...]
        
  5. Now comes the really important step: You have to define what your Layout looks like. Assume this to be your Dialog template (I added the ControlIDs as red text because I will refer to them later):



    If we want to have this Dialog layouted automatically, we have to define the panes. In this example I decided to define the panes like this:



    The root pane is marked red and contains 5 Items. The 2nd and 4th Items are Panes themself (marked blue), which contain respectively 2 and 3 Items. To define this layout you would have an OnInitDialog like this:
    
    BOOL CMyDialog::OnInitDialog() 
    {
    	CDialogMgr::OnInitDialog();
    
    	// define the Layout
    	CPane *newItemPane=new CPane ( this, HORIZONTAL );
    
    First of all the first Subpane is created. It is a horizontal pane. Next you add the Items:
    	newItemPane->addItem ( IDC_NEW_ITEM, GREEDY );
    	newItemPane->addItem ( IDC_ADD_ITEM, NORESIZE );
    
    The second SubPane is created similar:
    
    	CPane *bottomPane=new CPane ( this, HORIZONTAL );
    	bottomPane->addItem ( paneNull, GREEDY );
    	bottomPane->addItem ( IDOK, NORESIZE );
    	bottomPane->addItem ( IDCANCEL, NORESIZE );
Note the use of paneNull, this is not an actual Pane but a placeholder. The Term greedy in this context means that it will eat up all space that is left. The second Item is the OK Button, the CPaneBase::NORESIZE means, that it's  size is fixed. The actuall size could be appended as additional parameters, but if you leave them it will take the size from the current size of the control.    An Item can have several mode flags. A flag of 0 like in the first line  denotes a greedy Item, you could also add Items with an absolute size or some items with a relative size in percent. The Layout Engine first allocates space to ABSOLUTE_* items. What is left will  be allocated to RELATIVE items and the remaining space is divided into equal pieces for greedy items.

	m_pRootPane=new CPane ( this, VERTICAL );
Now the root pane is created (as denoted by m_pRootPane).
	m_pRootPane->addItem ( IDC_NEW_ITEM_STATIC, NORESIZE );
	m_pRootPane->addPane ( newItemPane, ABSOLUTE_VERT );
	m_pRootPane->addItem ( IDC_ITEM_LIST_STATIC, NORESIZE );
	m_pRootPane->addItem ( IDC_ITEM_LIST, GREEDY );
	m_pRootPane->addPane ( bottomPane, ABSOLUTE_VERT );
We want the 4th item (the ListBox) to grow and shrink. The 2nd and 5th item will preserve their height, but the panes will size in the horizontal direction. We use a special feature here: ABSOLUTE_VERT denotes that the vertical  size is fixed. As we did not provide one, the maximum height of all Items of the panes are computed and used as the fixed height.    
	UpdateLayout ();
Do not forget to call this one or the Layout will only get effective when  you resize the Border.    

	return TRUE;  // return TRUE unless you set the focus to a control
		      // EXCEPTION: OCX Property Pages should return FALSE
}
    
  1. Optionally: Add a call to Load() and Save() to (Re-)Store the Dimensions in/from the registry.
  2. That's it! Now the Dialog is automatically resized. Note that it also computes a minumum tracking size! Please take a look at the headerfile (DialogMgr.h) as all the features and functions are described there.

Version 1.2

Reworked the support for TabCtrls and added some more constants. Added the ability for somewhat more complicated layouts. Consider this example:



What is different here? First of all we would like to have the ListCtrl placed inside the tab, which is a pane. So I derived a new class from CPane named CPaneTab, which also resizes a TabCtrl along with its own size and places all childs within the tab (see CTabCtrl::AdjustRect() ). It also manages the minimum tracking size so that the Tabs are always visible! The code to describe the layout above looks like:


	// define the layout
	////////////////////

	// the bottom pane
	CPane*	paneBottom = new CPane(CPane::HORIZONTAL);
	paneBottom->addItem( new CPaneItem( NULL, CPaneBase::GREEDY));			// Greedy
	paneBottom->addItem( new CPaneItem( IDOK, this, CPaneBase::NORESIZE));
	paneBottom->addItem( new CPaneItem( IDCANCEL, this, CPaneBase::NORESIZE));


	// (green) holding a spacer and the ListCtrl
	CPane*	paneList = new CPane(CPane::VERTICAL);
	paneList->addItem( new CPaneItem( &m_list, CPaneBase::GREEDY));

	// (blue) the pane with the tab attached with an ExtraBorder
	CPane* paneTab  = new CPaneTab(&m_tab, CPane::HORIZONTAL);
	paneTab->addItem( paneList );

	// (red) the root pane 
	m_pRootPane = new CPane(CPane::VERTICAL);
	m_pRootPane->addItem( paneTab );
	m_pRootPane->addItem( paneBottom, CPaneBase::ABSOLUTE_VERT );

	UpdateLayout();

Download demo project - 81 KB

Date Last Updated: April 5, 1999