VS2015 中的MFC对话框动态布局

From: http://mariusbancila.ro/blog/2015/07/27/dynamic-dialog-layout-for-mfc-in-visual-c-2015/

DynamicDialog Layout for MFC in Visual C++ 2015

Marius Bancila C++MFCUncategorized2015-07-27

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

Suppose we have the following dialog:

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

Resized dialog

The resource editor provides support for this, but it canalso be done programmatically. If you open the properties of a control there isa new category called Dynamic Layout that allows you to selecta moving and a sizing type.

The options you have for both moving and resizing are: NoneHorizontal Vertical,and Both. These options should be self explanatory. However, theimportant thing to notice is the value for the X and Y axes moving andresizing: these are ratios, not dialog units or pixels, having a value between1 and 100 and defining how much a control is moved or resized when the hostdialog changes size.

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

·       fullyresize (100%) the group box and list box both horizontally and vertically

·       fullyresize the edit control horizontally

·       completely(100%) move the OK button vertically

·       completelymove the Add button horizontally

·       completelymove the Clear and Cancel buttons both horizontally and vertically


It is pretty simple to put values into the dynamic layout settings for eachcontrol. When you build and run and resize the dialog box the controls move orresize accordingly.

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

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/////////////////////////////////////////////////////////////////////////////

//

// 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 forwhich the settings are defined and the numbers in the BEGIN-END block represent:

·       thefirst line is a header containing the version number on the structure (0 inthis version)

·       theconsecutive lines are the dynamic layout settings (move and size ratios) foreach control on the dialog, corresponding to the order the controls weredefined for the dialog in the resource script file.

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

C++

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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 CPaneDialogCDialogBar and CFormView on the other hand this is done in HandleInitDialog.

This LoadDynamicLayoutResource is actually a member of CWnd which contains other methods forworking 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 thedynamic layout manager as a response to WM_SIZE

·       InitDynamicLayout: initializes dynamic layout manager as a response to the WM_CREATE message

C++

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

         // controls dynamic layout:

 

         ///

         /// Enables or disables layout manager for a window.

         /// TRUE - enable layout management, FALSE - disable layout management.

         void EnableDynamicLayout(BOOL bEnable = TRUE);

 

         ///

         /// This function returns TRUE, if layout management is enabled for a window; otherwise FALSE.

         ///

         /// Call EnableDynamicLayout in order to enable or disable layout management for a window.

         ///

         /// TRUE, if layout management is enabled for a window; otherwise FALSE.

         BOOL IsDynamicLayoutEnabled() const { return m_pDynamicLayout != NULL; }

 

         ///

         /// Call this function to retrieve a pointer to layout manager.

         ///

         /// Call EnableDynamicLayout in order to enable or disable layout management for a window.

         ///

         /// Returns a pointer to the window layout manager or NULL if layout wasn't enabled.

         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 dynamiclayout management on the fly.

1.    Initiallythe dynamic layout management is set so the controls move and resize when thedialog is resized.

2.    Disablethe dynamic layout management and the child controls are no longer adjusted.

3.    Re-enablethe dynamic layout management and it works again.

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

C++

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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 enablingand disabling dynamic layout management should look like this:

C++

 

1

2

3

4

5

6

7

8

9

10

11

12

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 valuesfor dynamic layout management can be done programmatically using the CMFCDynamicLayout class. This is importantwhen the controls are created dynamically and not in the resource template.What you have to do is:

·       createthe CMFCDynamicLayout object

·       storethe host window (the dialog) in that object

·       add thechild controls with their move and size settings

The following code provides the same dynamic layoutfunctionality as shown earlier except that all is set from code. Note that youmust call EnableDynamicLayoutHelper from OnInitDialog.

C++

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

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 expresseddifferently with the help of several static methods from CMFCDynamicLayout that create instances of MoveSettings and SizeSettings.

C++

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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 doesnot call CWnd::LoadDynamicLayoutResource because there are no settings in theresource script file. All these settings are only provided programmatically inthis case.

When controls have to move or resizecompletely (100%) across one or both axes, setting the right values for thelayout is straight forward. It gets complicated though when controls are notpositioned 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 thebottom vertically and centered horizontally. When the dialog resizes theyshould retain the original size, but they should always remain at the centerbottom.

In this case the Y ratio for move is again 100. But whatis 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 widthincreases by 100 units. That is the ratio we have to set.

Initially the dialog has 251 units, that means two halvesof 125 and 126 units. We want to keep the buttons apart by 10 units. What meansthe OK button is left aligned at 70 units and the Cancel button is left alightat 130 units.

Then we increase the size of the dialog by 100 units. It’s now 351 and thehaves have 175 and 176. The buttons are still 10 units apart and their with isstill 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.

The conclusion is their both left margin has moved 50 units, and that is thevalue we need to set for the X ratio of their move setting. (Remember, thevalue is a ratio, but 50 units out of 100 units is also 50%.)

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

In this example, initially, the dialog has 231 units andthat means two halves of 115 and 116 units. The buttons have both 60 unitswidth, so they are aligned at 27 or 28 units to the margins.

When the width of the dialog increases by 100 units to 331 units, the twohalves have 165 and 166 units. The buttons preserve their margins, so their newwidth is 110 units.

(Notice that the image above is stretched and the margins may be misleading.)

The conclusion is that:

·       The OKbutton did not move horizontally, but it increased its width from 60 units to110 units, that means 50%.

·       TheCancel button moved horizontally and is now left aligned at 193 units insteadof the original 143 units. That means it moved by 50% horizontally. Its sizedincreased from 60 units to 110 units, that also means 50%.

With those values set the buttons are resized andpositioned as intended.

For more information see MFC Dynamic Dialog Layout.

Demo source code:
MFC Dynamic Layout Management - demo 1 (1114)
MFC Dynamic Layout Management - demo 2 (975)
MFC Dynamic Layout Management - demo 3 (813)

你可能感兴趣的:(VS2015 中的MFC对话框动态布局)