Dynamic Dialog Layout for MFC in Visual C++ 2015

In Visual Studio 2015 MFC comes with a new features (something that has rarely happen in recent years): support for dynamic dialog layout. That means library support for moving and resizing controls on a dialog. In this article I will show how this feature works.

Suppose we have the following dialog:
Dialog in original size

What we want is that the controls on the dialog move (the buttons) or resize (the group box, edit and the list) when the dialog is resized:

Resized dialog
Resized dialog

The resource editor provides support for this, but it can also be done programmatically. If you open the properties of a control there is a new category called Dynamic Layout that allows you to select a moving and a sizing type.
Dynamic Layout Settings
The options you have for both moving and resizing are: None, Horizontal Vertical, and Both. These options should be self explanatory. However, the important thing to notice is the value for the X and Y axes moving and resizing: these are ratios, not dialog units or pixels, having a value between 1 and 100 and defining how much a control is moved or resized when the host dialog changes size.

Now, to enable the layout shown in the the example above we need to do the following:

  • fully resize (100%) the group box and list box both horizontally and vertically
  • fully resize the edit control horizontally
  • completely (100%) move the OK button vertically
  • completely move the Add button horizontally
  • completely move the Clear and Cancel buttons both horizontally and vertically

Dynamic layout settings for the example dialog
It is pretty simple to put values into the dynamic layout settings for each control. When you build and run and resize the dialog box the controls move or resize accordingly.

These dynamic layout settings are put in the resource script (.rc file) of the application. For the example above it looks like this:

/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//

IDD_MFCDYNLAYOUTDEMO_DIALOG AFX_DIALOG_LAYOUT
BEGIN
    0,
    0, 100, 0, 0,
    100, 100, 0, 0,
    0, 0, 100, 100,
    0, 0, 0, 0,
    0, 0, 100, 0,
    0, 0, 100, 100,
    100, 100, 0, 0,
    100, 0, 0, 0
END

In this definition IDD_MFCDYNLAYOUTDEMO_DIALOG is the identifier of the dialog for which the settings are defined and the numbers in the BEGIN-END block represent:

  • the first line is a header containing the version number on the structure (0 in this version)
  • the consecutive lines are the dynamic layout settings (move and size ratios) for each control on the dialog, corresponding to the order the controls were defined for the dialog in the resource script file.

Dynamic layout settings in .rc file

These settings are loaded into a CMFCDynamicLayout object (see afxlayout.h/cpp). This is done in the OnInitDialog method of the CDialog class as shown below:

BOOL CDialog::OnInitDialog()
{
	// execute dialog RT_DLGINIT resource
	BOOL bDlgInit;
	if (m_lpDialogInit != NULL)
		bDlgInit = ExecuteDlgInit(m_lpDialogInit);
	else
		bDlgInit = ExecuteDlgInit(m_lpszTemplateName);

	if (!bDlgInit)
	{
		TRACE(traceAppMsg, 0, "Warning: ExecuteDlgInit failed during dialog init.\n");
		EndDialog(-1);
		return FALSE;
	}

	LoadDynamicLayoutResource(m_lpszTemplateName);

Note: for CPaneDialog, CDialogBar and CFormView on the other hand this is done in HandleInitDialog.

This LoadDynamicLayoutResource is actually a member of CWnd which contains other methods for working with dynamic layouts:

  • EnableDynamicLayout: enables or disables layout manager for a window
  • IsDynamicLayoutEnabled: indicates if layout management is enabled for a window
  • GetDynamicLayout: retrieves a pointer to layout manager
  • ResizeDynamicLayout: readjust the position of the controls handled by the dynamic layout manager as a response to WM_SIZE
  • InitDynamicLayout: initializes dynamic layout manager as a response to the WM_CREATE message
	// controls dynamic layout:

	/// <summary>
	/// Enables or disables layout manager for a window.</summary>
	/// <param name="bEnable"> TRUE - enable layout management, FALSE - disable layout management.</param>
	void EnableDynamicLayout(BOOL bEnable = TRUE);

	/// <summary>
	/// This function returns TRUE, if layout management is enabled for a window; otherwise FALSE.</summary>
	/// <remarks>
	/// Call EnableDynamicLayout in order to enable or disable layout management for a window.</remarks>
	/// <returns> 
	/// TRUE, if layout management is enabled for a window; otherwise FALSE.</returns>
	BOOL IsDynamicLayoutEnabled() const { return m_pDynamicLayout != NULL; }

	/// <summary>
	/// Call this function to retrieve a pointer to layout manager.</summary>
	/// <remarks>
	/// Call EnableDynamicLayout in order to enable or disable layout management for a window.</remarks>
	/// <returns> 
	/// Returns a pointer to the window layout manager or NULL if layout wasn't enabled.</returns>
	CMFCDynamicLayout* GetDynamicLayout() { return m_pDynamicLayout; }

	/// 
	/// The method is called to adjust positions of child controls. 
	/// It recalculates positions of child controls if layout management is enabled for a window.
	virtual void ResizeDynamicLayout();
	void InitDynamicLayout();
	BOOL LoadDynamicLayoutResource(LPCTSTR lpszResourceName);

These methods allow you to enable or disable the dynamic layout management on the fly.

  1. Initially the dynamic layout management is set so the controls move and resize when the dialog is resized.
    dynlayout6

    dynlayout7

  2. Disable the dynamic layout management and the child controls are no longer adjusted.
    dynlayout8
  3. Re-enable the dynamic layout management and it works again.
    dynlayout9

The catch here is that just calling CWnd::EnableDynamicLayout won’t work because this method only deletes and recreates the CMFCDynamicLayout instance.

void CWnd::EnableDynamicLayout(BOOL bEnable)
{
	if (m_pDynamicLayout != NULL)
	{
		delete m_pDynamicLayout;
		m_pDynamicLayout = NULL;
	}

	if (!bEnable)
	{
		return;
	}

	m_pDynamicLayout = new CMFCDynamicLayout;
}

Just like CDialog::OnInitDialog you’d have to call CWnd::LoadDynamicLayoutResource. Therefore, the correct code for enabling and disabling dynamic layout management should look like this:

void CMFCDynLayoutDemoDlg::EnableDynamicLayoutHelper(bool const enable)
{
   if (enable && this->IsDynamicLayoutEnabled())
      return;

   this->EnableDynamicLayout(enable ? TRUE : FALSE);

   if (enable) 
   {
      this->LoadDynamicLayoutResource(m_lpszTemplateName);
   }
}

As mentioned earlier, setting the move and size values for dynamic layout management can be done programmatically using the CMFCDynamicLayout class. This is important when the controls are created dynamically and not in the resource template. What you have to do is:

  • create the CMFCDynamicLayout object
  • store the host window (the dialog) in that object
  • add the child controls with their move and size settings

The following code provides the same dynamic layout functionality as shown earlier except that all is set from code. Note that you must call EnableDynamicLayoutHelper from OnInitDialog.

void CMFCDynLayoutDemoDlg::EnableDynamicLayoutHelper(bool const enable)
{
   if (enable && this->IsDynamicLayoutEnabled())
      return;

   this->EnableDynamicLayout(enable ? TRUE : FALSE);

   if (enable)
   {
      SetupDynamicLayout();
   }
}

void CMFCDynLayoutDemoDlg::SetupDynamicLayout()
{
   auto manager = this->GetDynamicLayout();
   if (manager != nullptr)
   {
      auto movenone = CMFCDynamicLayout::MoveSettings{};
      auto moveboth100 = CMFCDynamicLayout::MoveSettings {};
      moveboth100.m_nXRatio = 100;
      moveboth100.m_nYRatio = 100;
      auto movex100 = CMFCDynamicLayout::MoveSettings {};
      movex100.m_nXRatio = 100;
      auto movey100 = CMFCDynamicLayout::MoveSettings {};
      movey100.m_nYRatio = 100;

      auto sizenone = CMFCDynamicLayout::SizeSettings{};
      auto sizeboth100 = CMFCDynamicLayout::SizeSettings{};
      sizeboth100.m_nXRatio = 100;
      sizeboth100.m_nYRatio = 100;
      auto sizex100 = CMFCDynamicLayout::SizeSettings{};
      sizex100.m_nXRatio = 100;

      manager->Create(this);

      manager->AddItem(IDC_STATIC_GROUPBOX, movenone, sizeboth100);
      manager->AddItem(IDC_LIST1, movenone, sizeboth100);
      manager->AddItem(IDC_EDIT1, movenone, sizex100);
      manager->AddItem(IDC_BUTTON_ADD, movex100, sizenone);
      manager->AddItem(IDC_BUTTON_CLEAR, moveboth100, sizenone);
      manager->AddItem(IDOK, movey100, sizenone);
      manager->AddItem(IDCANCEL, moveboth100, sizenone);
   }
}

Actually the same code as above can be expressed differently with the help of several static methods from CMFCDynamicLayout that create instances of MoveSettings and SizeSettings.

void CMFCDynLayoutDemoDlg::SetupDynamicLayout()
{
   auto manager = this->GetDynamicLayout();
   if (manager != nullptr)
   {
      manager->Create(this);

      manager->AddItem(IDC_STATIC_GROUPBOX, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontal(100));
      manager->AddItem(IDC_LIST1, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
      manager->AddItem(IDC_EDIT1, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontal(100));
      manager->AddItem(IDC_BUTTON_ADD, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
      manager->AddItem(IDC_BUTTON_CLEAR, CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
      manager->AddItem(IDOK, CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeNone());
      manager->AddItem(IDCANCEL, CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
   }
}

One important thing to notice here is that this code does not call CWnd::LoadDynamicLayoutResource because there are no settings in the resource script file. All these settings are only provided programmatically in this case.

When controls have to move or resize completely (100%) across one or both axes, setting the right values for the layout is straight forward. It gets complicated though when controls are not positioned sideways or need to move or resize with more complicated rules. Let’s take an example where the OK and Cancel buttons are positioned at the bottom vertically and centered horizontally. When the dialog resizes they should retain the original size, but they should always remain at the center bottom.

dynlayout10 dynlayout11

In this case the Y ratio for move is again 100. But what is the move ratio on the X axis? To determine you need a paper and pen. Basically we need to find how much do the buttons move on X when the width increases by 100 units. That is the ratio we have to set.

Initially the dialog has 251 units, that means two halves of 125 and 126 units. We want to keep the buttons apart by 10 units. What means the OK button is left aligned at 70 units and the Cancel button is left alight at 130 units.
dynlayout12
Then we increase the size of the dialog by 100 units. It’s now 351 and the haves have 175 and 176. The buttons are still 10 units apart and their with is still 50 units each. That means the OK button is now left aligned at 120 units, and the Cancel button is left aligned at 180 units.
dynlayout13
The conclusion is their both left margin has moved 50 units, and that is the value we need to set for the X ratio of their move setting. (Remember, the value is a ratio, but 50 units out of 100 units is also 50%.)

What if the OK and Cancel buttons should both be aligned at the center of their each half on the X axis and always preserve the margins? In other words they should change like this:

dynlayout14 dynlayout15

In this example, initially, the dialog has 231 units and that means two halves of 115 and 116 units. The buttons have both 60 units width, so they are aligned at 27 or 28 units to the margins.
dynlayout16
When the width of the dialog increases by 100 units to 331 units, the two halves have 165 and 166 units. The buttons preserve their margins, so their new width is 110 units.
dynlayout17
(Notice that the image above is stretched and the margins may be misleading.)

The conclusion is that:

  • The OK button did not move horizontally, but it increased its width from 60 units to 110 units, that means 50%.
  • The Cancel button moved horizontally and is now left aligned at 193 units instead of the original 143 units. That means it moved by 50% horizontally. Its sized increased from 60 units to 110 units, that also means 50%.

With those values set the buttons are resized and positioned as intended.

For more information see MFC Dynamic Dialog Layout.

Demo source code:
MFC Dynamic Layout Management - demo 1 (5841 downloads )
MFC Dynamic Layout Management - demo 2 (5180 downloads )
MFC Dynamic Layout Management - demo 3 (5306 downloads )

1 Reply to “Dynamic Dialog Layout for MFC in Visual C++ 2015”

  1. Does this work in Win32? What are the Win32 equivalents of the MFC function?

    I have created a dialog with dynamic attributes (Sizing Type). The individual controls grow when the dialog is tested in the VS Resource Editor. However their size remains constant when the dialog is stretched when the program is actually running.

    See:
    https://stackoverflow.com/questions/49833690

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.