How to create a resizable CDialog in MFC?
Asked Answered
P

7

29

I have to create a dialog based application, instead of old CFormView type of design. But CDialog produces fixed-size dialogs. How can I create dialog based applications with resizable dialogs?

Pie answered 26/9, 2008 at 6:31 Comment(5)
The question has nothing to do with C++. Hence, I have removed that tag.Dow
Maybe throw a Win32 tag on this, the RC file stuff is basic Win32 and not even MFC specific.Legalism
@Aardvark: The RC stuff may not be MFC-specific, but the title specifies MFC, CDialog (specified in the question) is an MFC class (see the MSVS help), and some good answers require considerable interaction with MFC methods – so it was right to let that stand. As to C++, MSVS generates C++ code for MFC, but does it do that for any other language? If not, [C++] was not altogether irrelevant — but redundant, so fair dos.Upstate
@Upstate Right to let what stand, the MFC tag? I suggested (so may years ago, why are you even chiming in on this now?) adding a win32/winapi tag, not to remove the MFC tag.Legalism
@Aardvark: Yes, right to let the tag stand. I responded because I took “not even MFC specific” to refer to the entire question. I don’t think the age of the comment matters. My comment was as much for anybody thinking about the question+comments+tags as for you, hope you were not put out.Upstate
M
27

In the RC resource file if the dialog has this style similar to this it will be fixed size:

IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

If the dialog has this style it will be sizeable:

IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME

With these sizable frame options the dialog will be re-sizeable but you will still need to do a lot of work handling the WM_SIZE message to manage the sizing an positioning of the controls within the dialog.

Megaron answered 26/9, 2008 at 6:46 Comment(1)
I do not recommend that you edit .rc files directly - while the format seems straight forward enough the parser is very finicky. Besides since you are using MFC, it's safe to assume that you are also using Visual Studio. In which case you should simply open your dialog resource in Visual Studio, and under the dialog properties choose "Resizing" for the "Border" property.Creon
N
25

In addition to setting the style to WS_THICKFRAME, you'll probably also want to have a system to move and resize the controls in a dialog as the dialog is resized. For my own personal use I've created a base class to replace CDialog that has this capability. Derive from this class and in your InitDialog function call the AutoMove function for each child control to define how much it should move and how much it should resize relative to the parent dialog. The size of the dialog in the resource file is used as a minimum size.

BaseDialog.h:

#if !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)
#define AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <vector>

class CBaseDialog : public CDialog
{
// Construction
public:
    CBaseDialog(UINT nIDTemplate, CWnd* pParent = NULL);   // standard constructor

    void AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct);

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CBaseDialog)
protected:
    //}}AFX_VIRTUAL

protected:
    //{{AFX_MSG(CBaseDialog)
    virtual BOOL OnInitDialog();
    afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()

public:
    bool            m_bShowGripper;         // ignored if not WS_THICKFRAME

private:
    struct SMovingChild
    {
        HWND        m_hWnd;
        double      m_dXMoveFrac;
        double      m_dYMoveFrac;
        double      m_dXSizeFrac;
        double      m_dYSizeFrac;
        CRect       m_rcInitial;
    };
    typedef std::vector<SMovingChild>   MovingChildren;

    MovingChildren  m_MovingChildren;
    CSize           m_szInitial;
    CSize           m_szMinimum;
    HWND            m_hGripper;
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)

BaseDialog.cpp:

#include "stdafx.h"
#include "BaseDialog.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CBaseDialog::CBaseDialog(UINT nIDTemplate, CWnd* pParent /*=NULL*/)
    : CDialog(nIDTemplate, pParent),
      m_bShowGripper(true),
      m_szMinimum(0, 0),
      m_hGripper(NULL)
{
}


BEGIN_MESSAGE_MAP(CBaseDialog, CDialog)
    //{{AFX_MSG_MAP(CBaseDialog)
    ON_WM_GETMINMAXINFO()
    ON_WM_SIZE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CBaseDialog::AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct)
{
    ASSERT((dXMovePct + dXSizePct) <= 100.0);   // can't use more than 100% of the resize for the child
    ASSERT((dYMovePct + dYSizePct) <= 100.0);   // can't use more than 100% of the resize for the child
    SMovingChild s;
    GetDlgItem(iID, &s.m_hWnd);
    ASSERT(s.m_hWnd != NULL);
    s.m_dXMoveFrac = dXMovePct / 100.0;
    s.m_dYMoveFrac = dYMovePct / 100.0;
    s.m_dXSizeFrac = dXSizePct / 100.0;
    s.m_dYSizeFrac = dYSizePct / 100.0;
    ::GetWindowRect(s.m_hWnd, &s.m_rcInitial);
    ScreenToClient(s.m_rcInitial);
    m_MovingChildren.push_back(s);
}

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

    // use the initial dialog size as the default minimum
    if ((m_szMinimum.cx == 0) && (m_szMinimum.cy == 0))
    {
        CRect rcWindow;
        GetWindowRect(rcWindow);
        m_szMinimum = rcWindow.Size();
    }

    // keep the initial size of the client area as a baseline for moving/sizing controls
    CRect rcClient;
    GetClientRect(rcClient);
    m_szInitial = rcClient.Size();

    // create a gripper in the bottom-right corner
    if (m_bShowGripper && ((GetStyle() & WS_THICKFRAME) != 0))
    {
        SMovingChild s;
        s.m_rcInitial.SetRect(-GetSystemMetrics(SM_CXVSCROLL), -GetSystemMetrics(SM_CYHSCROLL), 0, 0);
        s.m_rcInitial.OffsetRect(rcClient.BottomRight());
        m_hGripper = CreateWindow(_T("Scrollbar"), _T("size"), WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP,
                                  s.m_rcInitial.left, s.m_rcInitial.top, s.m_rcInitial.Width(), s.m_rcInitial.Height(),
                                  m_hWnd, NULL, AfxGetInstanceHandle(), NULL);
        ASSERT(m_hGripper != NULL);
        if (m_hGripper != NULL)
        {
            s.m_hWnd = m_hGripper;
            s.m_dXMoveFrac = 1.0;
            s.m_dYMoveFrac = 1.0;
            s.m_dXSizeFrac = 0.0;
            s.m_dYSizeFrac = 0.0;
            m_MovingChildren.push_back(s);

            // put the gripper first in the z-order so it paints first and doesn't obscure other controls
            ::SetWindowPos(m_hGripper, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
        }
    }

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CBaseDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
    CDialog::OnGetMinMaxInfo(lpMMI);

    if (lpMMI->ptMinTrackSize.x < m_szMinimum.cx)
        lpMMI->ptMinTrackSize.x = m_szMinimum.cx;
    if (lpMMI->ptMinTrackSize.y < m_szMinimum.cy)
        lpMMI->ptMinTrackSize.y = m_szMinimum.cy;
}

void CBaseDialog::OnSize(UINT nType, int cx, int cy) 
{
    CDialog::OnSize(nType, cx, cy);

    int iXDelta = cx - m_szInitial.cx;
    int iYDelta = cy - m_szInitial.cy;
    HDWP hDefer = NULL;
    for (MovingChildren::iterator p = m_MovingChildren.begin();  p != m_MovingChildren.end();  ++p)
    {
        if (p->m_hWnd != NULL)
        {
            CRect rcNew(p->m_rcInitial);
            rcNew.OffsetRect(int(iXDelta * p->m_dXMoveFrac), int(iYDelta * p->m_dYMoveFrac));
            rcNew.right += int(iXDelta * p->m_dXSizeFrac);
            rcNew.bottom += int(iYDelta * p->m_dYSizeFrac);
            if (hDefer == NULL)
                hDefer = BeginDeferWindowPos(m_MovingChildren.size());
            UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER;
            if ((p->m_dXSizeFrac != 0.0) || (p->m_dYSizeFrac != 0.0))
                uFlags |= SWP_NOCOPYBITS;
            DeferWindowPos(hDefer, p->m_hWnd, NULL, rcNew.left, rcNew.top, rcNew.Width(), rcNew.Height(), uFlags);
        }
    }
    if (hDefer != NULL)
        EndDeferWindowPos(hDefer);

    if (m_hGripper != NULL)
        ::ShowWindow(m_hGripper, (nType == SIZE_MAXIMIZED) ? SW_HIDE : SW_SHOW);
}
Nursery answered 21/4, 2011 at 4:42 Comment(4)
nice use of DeferWindowPos to avoid redrawsPrizewinner
Nice and simple to use. I've blogged it: technical-recipes.com/2011/…Antiquated
Seems that you're leaking the gripper window (m_hGripper).Flagman
@CodyGray you're right, I definitely didn't follow the rule of 3/5/0 in this code. But in this case I don't think there are any harmful effects. The gripper window itself will be destroyed when the parent window is destroyed, probably well before the C++ destructor is called. And the handle itself is just a simple integer or pointer that will not actually leak anything.Nursery
G
6

Since Visual Studio 2015, you can use MFC Dynamic Dialog Layout, but it seems, there is no way to restrict dialog size to minimal size (still only the old way by handling WM_GETMINMAXINFO).

Dynamic layout can be done:

  • at design time in resource editor by selecting the control and setting the Moving Type and Sizing Type properties (this emits new AFX_DIALOG_LAYOUT section into .rc file);
  • or programatically using the CMFCDynamicLayout class.

Documentation: Dynamic Layout

Gefen answered 28/3, 2017 at 16:1 Comment(2)
Is this component part of the controls accessible in the "Toolbox" of Visual Studio. I can't find it, but only old school components. Is there an extension to be able to compose graphically with such modern ones?Uncivil
The functionality is not available as a separate component. It is available for standard Toolbox controls as a Moving Type and Sizing Type property, which can be found in the Properties window after selecting a certain control in the dialog editor.Gefen
T
4

If your using a dialog template then open the dialog template in the resource editor and set the Style property to Popup and the Border property to Resizing. I'm pretty sure this will do the same as what jussij said and set the WS_POPUP and WS_THICKFRAME styles. To set these dynamically then override the PreCreateWindow function and add the following:

cs.style |= WS_POPUP | WS_THICKFRAME;
Tribble answered 21/4, 2011 at 4:7 Comment(1)
flounder.com/getminmaxinfo.htm has a visual example of changing the Border using the dialog's Properties (in the beginning of the page).Hydrotherapy
S
3

There is no easy way to do this. Basically, you will need to dynamically layout controls when the window size is changed.

See http://www.codeproject.com/KB/dialog/resizabledialog.aspx for an example

Solanum answered 26/9, 2008 at 6:38 Comment(0)
A
1

I have some blog instructions on how to create a very minimalist re-sizeable dialog in MFC.

It is basically an implementation of Paulo Messina's posting at CodeProject but with as much extraneous stuff removed as possible, just to help clarify how to do it better.

It is fairly straightforward to implement once you've had a bit of practice: the important bits are to:

i. ensure you have his CodeProject libraries etc pulled into your project and it all compiles correctly.

ii. do the extra initialization required inside the OnInitDialog method: make the gripper visible, set the maximum dilog size, add anchor points to the dialog control items that you wish to 'stretch' etc.

iii. Replace usage of CDialog with CResizableDialog at the appropriate points: in the dialog class definition, constructor, DoDataExchange, BEGIN_MESSAGE_MAP, OnInitDialog etc.

Antiquated answered 11/8, 2011 at 8:28 Comment(0)
P
0

I've tried many MFC layout libraries and found this one the best: http://www.codeproject.com/KB/dialog/layoutmgr.aspx. Check out the comments there for some bug fixes and improvements (disclaimer: some of them by me ;) ). When you use this library, setting the correct resize flags on your window will be handled for you.

Pistol answered 26/9, 2008 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.